JWT 请求无法通过授权策略

Posted

技术标签:

【中文标题】JWT 请求无法通过授权策略【英文标题】:JWT request can't go through Authorize policy 【发布时间】:2019-04-18 12:21:44 【问题描述】:

我正在 Asp.Net 上实施 JWT 授权,但我在这里遇到了麻烦。当我尝试访问仪表板/主页控制器的方法时,我总是收到“401 Unauthorized”响应。根据我的研究,我可以说请求无法通过

[Authorize(Policy = "ApiUser")]

但是当评论那句话时,我在这里收到一个错误

var userId = _caller.Claims.Single(c => c.Type == "id");

这表明该序列不包含适当的元素,以及是否要打印

_httpContextAccessor.HttpContext.User.Claims

我基本上收到一个“[]”。 另外我想让你注意到令牌是 correct 我正在 postman 的帮助下测试应用程序 我在这个错误上堆积了两天,真的依赖你的帮助。告诉我是否需要一些额外的代码。 代码: 仪表板控制器:

 [Authorize(Policy = "ApiUser")]
        [Route("api/[controller]/[action]")]
        public class DashboardController : Controller
        
            private readonly ClaimsPrincipal _caller;
            private readonly BackendContext _appDbContext;
            private readonly IHttpContextAccessor _httpContextAccessor;

            public DashboardController(UserManager<AppUser> userManager, BackendContext appDbContext, IHttpContextAccessor httpContextAccessor)
            
                _caller = httpContextAccessor.HttpContext.User;
                _appDbContext = appDbContext;
                _httpContextAccessor = httpContextAccessor;
            

            // GET api/dashboard/home

            [HttpGet]
            public async Task<IActionResult> Home()
            
                // retrieve the user info
                //HttpContext.User
                //return new OkObjectResult(_httpContextAccessor.HttpContext.User.Claims);
                    var userId = _caller.Claims.Single(c => c.Type == "id");

                var customer = await _appDbContext.Customers.Include(c => c.Identity).SingleAsync(c => c.Identity.Id == userId.Value);

                return new OkObjectResult(new
                
                    Message = "This is secure API and user data!",
                    customer.Identity.FirstName,
                    customer.Identity.LastName,
                    customer.Identity.PictureUrl,
                    customer.Identity.FacebookId,
                    customer.Location,
                    customer.Locale,
                    customer.Gender
                );
            
        

启动:

public class Startup
    
        private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"; // todo: get this from somewhere secure
        private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));

        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        public void ConfigureServices(IServiceCollection services)
        
            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<BackendContext>()
                .AddDefaultTokenProviders();

            services.AddDbContext<BackendContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                  b => b.MigrationsAssembly("Backend")));

            services.AddSingleton<IJwtFactory, JwtFactory>();
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));

            // Configure JwtIssuerOptions
            services.Configure<JwtIssuerOptions>(options =>
            
                options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
                options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
            );

            var tokenValidationParameters = new TokenValidationParameters
            
                ValidateIssuer = true,
                ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],

                ValidateAudience = true,
                ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],

                ValidateIssuerSigningKey = true,
                IssuerSigningKey = _signingKey,

                RequireExpirationTime = false,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            ;

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

            ).AddJwtBearer(configureOptions =>
            
                configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                configureOptions.TokenValidationParameters = tokenValidationParameters;
                configureOptions.SaveToken = true;
            );

            // api user claim policy
            services.AddAuthorization(options =>
            
                options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
            );

            // add identity
            var builder = services.AddIdentityCore<AppUser>(o =>
            
                // configure identity options
                o.Password.RequireDigit = false;
                o.Password.RequireLowercase = false;
                o.Password.RequireUppercase = false;
                o.Password.RequireNonAlphanumeric = false;
                o.Password.RequiredLength = 6;
            );
            builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
            builder.AddEntityFrameworkStores<BackendContext>().AddDefaultTokenProviders();

            services.AddAutoMapper();

            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
            services.AddDbContext<BackendContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                b => b.MigrationsAssembly("Backend")));
            services.AddTransient<IStoreService, StoreService>();
            services.AddMvc();//.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
        
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            app.UseAuthentication();
            app.UseDefaultFiles();
            app.UseStaticFiles();

            app.UseMvc();
        
    

常量(用于启动):

 public static class Constants
        
            public static class Strings
            
                public static class JwtClaimIdentifiers
                
                    public const string Rol = "rol", Id = "id";
                

                public static class JwtClaims
                
                    public const string ApiAccess = "api_access";
                
            
          

【问题讨论】:

【参考方案1】:

您的问题是由IssuerAudience 的令牌生成和验证之间的配置不匹配引起的。

Startup.cs 中,您配置了ValidateIssuer = trueValidateAudience = true,但是对于您提供的令牌,没有issaud

这里有两种方法可以试一试:

禁用验证IssuerAudience

    var tokenValidationParameters = new TokenValidationParameters
    
        ValidateIssuer = false,
        ValidIssuer = "Issuer",

        ValidateAudience = false,
        ValidAudience = "Audience",

        ValidateIssuerSigningKey = true,
        IssuerSigningKey = _signingKey,

        RequireExpirationTime = false,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.Zero
    ;

或者,在生成令牌时提供IssuerAudience

    public IActionResult Login()

    var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"));
    var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

    var tokeOptions = new JwtSecurityToken(
        issuer: "Issuer",
        audience: "Audience",
        claims: new List<Claim>()  new Claim("rol", "api_access") ,
        expires: DateTime.Now.AddMinutes(25),
        signingCredentials: signinCredentials
    );

    var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
    return Ok(new  Token = tokenString );

这是一个工作演示 CoreJwt。

【讨论】:

【参考方案2】:

问这个问题已经过去了大约 4 个月,但我希望它对未来的访问者有所帮助,我遇到了同样的问题,这是因为我错过了将 app.UseAuthentication(); 添加到我的 Configure 方法:

app.UseAuthentication();
//////////////////////////
app.UseHttpsRedirection();
app.UseStaticFiles();

我也错过了将JwtIssuerOptions 添加到我的appsettings.jsonappsettings.Development.json

"JwtIssuerOptions": 
"Issuer": "webApi",
"Audience": "http://localhost:5000/"
,

【讨论】:

以上是关于JWT 请求无法通过授权策略的主要内容,如果未能解决你的问题,请参考以下文章

AuthTokenAccessException:无法解码JWT承载:没有JWT承载存在于“授权”的请求头

通过 JWT 令牌在 Azure API 管理中进行授权

如何使用自定义策略模式实现 jwt 令牌基础身份验证以在 .net 核心中进行授权?

ReactJS-在http请求的axios方法中将JWT令牌作为授权传递

未使用 jwt_required 注释时,烧瓶路由请求授权标头

使用 python 请求进行 Web 抓取的 JWT 不记名授权