ASP.NET Core Authorize 属性不适用于 JWT

Posted

技术标签:

【中文标题】ASP.NET Core Authorize 属性不适用于 JWT【英文标题】:ASP.NET Core Authorize attribute not working with JWT 【发布时间】:2017-03-31 11:43:19 【问题描述】:

我想在 ASP.Net Core 中实现基于 JWT 的安全性。目前,我想做的就是读取Authorization 标头中的不记名令牌,并根据我的标准对其进行验证。我不需要(也不想)包含 ASP.Net Identity。事实上,除非我真的需要它们,否则我会尽可能多地避免使用 MVC 添加的东西。

我创建了一个最小的项目来演示这个问题。要查看原始代码,只需查看编辑历史记录。我期待这个示例拒绝所有对/api/icons 的请求,除非它们为Authorization HTTP 标头提供相应的不记名令牌。该示例实际上允许所有请求

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Routing;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System;
using Newtonsoft.Json.Serialization;

namespace JWTSecurity

    public class Startup
    
        public IConfigurationRoot Configuration  get; set; 

        public Startup(IHostingEnvironment env)
        
            IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath);
            Configuration = builder.Build();
        

        public void ConfigureServices(IServiceCollection services)
        
            services.AddOptions();
            services.AddAuthentication();
            services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
        

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        
            loggerFactory.AddConsole();
            app.UseJwtBearerAuthentication(new JwtBearerOptions
            
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                TokenValidationParameters = new TokenValidationParameters
                
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("supersecretkey")),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                
            );
            app.UseMvc(routes => routes.MapRoute("default", "controller=Home/action=Index/id?"));
        
    

控制器/图标控制器.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace JWTSecurity.Controllers

    [Route("api/[controller]")]
    public class IconsController : Controller
    
        [Authorize]
        public IActionResult Get()
        
            return Ok("Some content");
        
    

【问题讨论】:

你能显示你应用了授权属性的方法和类吗? 您的 JWT 令牌中间件在哪里?您的服务正在通过 /token 端点或类似的东西正确生成 JWT? 此时,我不关心生成令牌,我只关心拒绝任何在标头中不提供令牌的内容。 你现在拥有的和我拥有的非常相似,除了我有 ValidateIssuer = true 和 ValidateAudience = true,但我认为这些不是必需的。我没有 services.AddAuthentication();要么。 是的,我只是尝试添加它们,没有区别。一定有我想念的东西... 【参考方案1】:

我刚刚遇到了类似的问题,结果发现控制器级别的 [AllowAnonymous] 属性会覆盖应用于该控制器内任何操作的任何 [Authorize] 属性。这是我以前不知道的。

【讨论】:

【参考方案2】:

如果您使用自定义方案,则必须使用

[Authorize(AuthenticationSchemes="your custom scheme")]

【讨论】:

【参考方案3】:

找到了这个问题的完美解决方案 您的配置服务类应如下所示

public void ConfigureServices(IServiceCollection services)
    
        services.Configure<CookiePolicyOptions>(options =>
        
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        );

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>
        (options => options.Stores.MaxLengthForKeys = 128)
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultUI()
        .AddDefaultTokenProviders();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();


        services.AddAuthentication(options =>
        
            //options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            //options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            //options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

        )
        .AddCookie(cfg => cfg.SlidingExpiration = true)
        .AddJwtBearer(cfg =>
        
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters
            
                ValidIssuer = Configuration["JwtIssuer"],
                ValidAudience = Configuration["JwtIssuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
                ClockSkew = TimeSpan.Zero // remove delay of token when expire
            ;
        );


        services.Configure<IdentityOptions>(options =>
        
            // Password settings  
            options.Password.RequireDigit = true;
            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = true;
            options.Password.RequireLowercase = false;
            options.Password.RequiredUniqueChars = 6;

            // Lockout settings  
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
            options.Lockout.MaxFailedAccessAttempts = 10;
            options.Lockout.AllowedForNewUsers = true;

            // User settings  
            options.User.RequireUniqueEmail = true;
        );

        services.AddAuthentication().AddFacebook(facebookOptions =>
        
            facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
            facebookOptions.AppSecret =  Configuration["Authentication:Facebook:AppSecret"];
        );
        //Seting the Account Login page  
        services.ConfigureApplicationCookie(options =>
        
            // Cookie settings  
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
            options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login  
            options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout  
            options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied  
            options.SlidingExpiration = true;
        );



        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    

您可以像下面这样对 Web API 控制器进行身份验证

[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
public class TaskerController : ControllerBase

    [HttpGet("[action]")]
    //[AllowAnonymous]
    public IEnumerable<string> Get()
    
        return new string[]  "value1", "value2" ;
    

并且您可以像往常一样使用基于身份的授权属性,如下所示用于 MVC 控制器

public class TaskController : Controller


    [Authorize]
    public IActionResult Create()
    
    

关键解决方案是.AddCookie(cfg =&gt; cfg.SlidingExpiration = true) 在 JWT 身份验证之前添加,即 .AddJwtBearer(//removed for brevity) 将基于 Cookie 的授权设置为默认值,因此 [Authorize] 照常工作,每当您需要 JWT 时,您必须使用 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 显式调用它

希望它能帮助那些希望将网站作为前端并将移动就绪的 Web API 作为后端的人。

【讨论】:

问题中的关键点之一是I don't need (and don't want) to include ASP.Net Identity。使用标准配置设置 Asp.Net 很容易,但目标是创建一个使用 MvcCore 和 JwtAuthentication 的最小项目 答案涵盖了两种类型的身份验证机制,您可以根据需要跳过其中任何一种。您只需注释掉您不需要的部分@AndrewWilliamson 这不仅仅是注释掉不需要的部分的问题。您的ConfigureServices 方法调用.AddMvc() 而不是.AddMvcCore()。我的整个问题是基于当我使用.AddMvcCore() 时身份验证不起作用,我必须通读.AddMvc() 的源代码才能弄清楚有什么区别。当您提供答案时,它应包含最少 量的更改以回答问题。否则用户仍然需要弄清楚你的代码的哪一部分解决了问题 感谢您愿意与他人分享您的经验 - 不要停下来,只记得专注于用最小的例子回答问题 感谢[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 属性。就我而言,对于 WebApi,我想使用 JWTAuth,对于 ViewController,我想使用 CookieAuth。然后我创建了双重 AddAuthentication()。但不幸的是,由于我使用的是[Authorize] 而不添加参数。未经授权的页面总是点击 JWTAuth 设置。但是在[Authorize] 上添加参数后,这解决了我的问题【参考方案4】:

对于那些甚至尝试了预览答案但没有解决问题的人,下面是我的问题是如何解决的。

[Authorize(AuthenticationSchemes="Bearer")]

【讨论】:

我想你可以查看这个答案***.com/a/63446357/4307338 没问题,伙计。编码愉快。【参考方案5】:

找到了!

主要问题出在这一行:

services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());

我注意到,通过从 AddMvcCore() 切换到 AddMvc(),授权突然开始工作!在挖掘了the ASP.NET source code 之后,看看AddMvc() 做了什么,我意识到我需要第二个电话给IMvcBuilder.AddAuthorization()

services.AddMvcCore()
    .AddAuthorization() // Note - this is on the IMvcBuilder, not the service collection
    .AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());

【讨论】:

这也引起了我的注意(因为我使用的是AddMvcCore)。对 AddAuthorization() 的调用添加了 AuthorizationApplicationModelProvider,它在控制器上查找 Authorize/AllowAnonymous 并添加适当的策略。 不要忘记AddDataAnnotations,如果您将它们用于请求验证! Authorize 属性在使用services.AddMvcCore().AddAuthorization()后开始工作 我想你可以查看这个答案***.com/a/63446357/4307338 @HoqueMDZahidul 目的是使用MvcCore 生成一个最小示例。您链接到的答案显示了一个有效的 asp 网络配置,但它没有显示 使令牌身份验证工作的最低配置。如果我只想让身份验证工作,我可以继续使用.AddMvc() 而不是.AddMvcCore()。您的回答没有解决原来的问题【参考方案6】:

您也在使用身份验证,它隐式包含 cookie 验证。可能您使用身份方案登录并导致身份验证成功。

如果不需要身份认证,则删除身份认证(如果您只想要jwt认证),否则为Authorize属性指定Bearer方案,如下所示:

[Authorize(ActiveAuthenticationSchemes = "Bearer")]

【讨论】:

我明天回去工作的时候试试这个,希望你是对的。关于这一点,我使用身份的原因纯粹是为了隐藏/抽象密码散列。你会建议什么替代方案? Identity 是推荐的解决方案。如果你需要,那就去吧。据我了解,您想使用身份和 jwt 可选。在这种情况下,您应该指定要限制的身份验证方案。例如,如果您只需要 jwt 身份验证,则应该使用承载方案。 您不需要 ActiveAuthenticationSchemes 来使用 JWT。只要您正确发送令牌,就可以在没有它的情况下工作。 @SledgeHammer 当然,通常你不需要它。但我假设是由身份认证引起的问题,并且还假设 OP 只想进行 jwt 认证。 你是我的英雄

以上是关于ASP.NET Core Authorize 属性不适用于 JWT的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET Core Identity 中使用 Authorize 属性检查多个策略之一

如何创建不依赖于 ASP.NET Core 声明的自定义 Authorize 属性?

JS Fetch API 不适用于具有 Authorize 属性的 ASP.NET Core 2 控制器

ASP.NET Core 2:使用 [Authorize] 对 API 的 Ajax 调用使预检请求失败

拦截asp.net core Authorize action,授权成功后执行自定义动作

在信号器集线器类上设置 [Authorize] 属性仅在连接到集线器时有效,而不是在集线器上的每个方法之前(ASP.NET Core 2.2)