用于授权和身份验证的多个 JWT 承载

Posted

技术标签:

【中文标题】用于授权和身份验证的多个 JWT 承载【英文标题】:Multiple JWT bearers for authorization and authentication 【发布时间】:2019-05-22 10:29:37 【问题描述】:

我有一个用于 SSO 的 .NET Core IdentityServer (IS),我想用它来验证我的 .NET Core(后端)-Angular(客户端)应用程序。我想通过自定义后端生成的 JWT 令牌在后端拥有一个 EF ApplicationUser 并在后端拥有基于声明的授权,该令牌也适用于客户端的授权。

在后端,我创建了一个中间件来检查所有请求的“授权”标头。如果标头包含由 IS 生成的令牌,我想将其交换为包含必要声明的自定义(后端)生成的令牌。客户端然后使用此标头对后端进行后续请求。

启动配置:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

   app.UseMiddleware<AuthorizationHeaderMiddleware>();
   app.UseAuthentication();
   app.UseStaticFiles();
   app.UseMvc();


public void ConfigureServices(IServiceCollection services)

   services.AddAuthentication(DEFAULT_AUTH_SCHEME)
        .AddJwtBearer(DEFAULT_AUTH_SCHEME, cfg =>
          
                cfg.Audience = Configuration["Authorization:JwtIssuer"];
                cfg.RequireHttpsMetadata = false;
                cfg.TokenValidationParameters = new TokenValidationParameters
                    
                    RequireSignedTokens = false,
                    ValidateIssuer = false,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = false,
                    ValidIssuer = Configuration["Authorization:JwtIssuer"],
                    ValidAudience = Configuration["Authorization:JwtIssuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authorization:JwtKey"])),
                    RequireExpirationTime = false,
                    ClockSkew = TimeSpan.Zero // remove delay of token when expire
                ;
          )
       .AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
             
                 options.Authority = Configuration["IdentityServer:Url"];
                 options.RequireHttpsMetadata = false;
                 options.ApiName = Configuration["IdentityServer:ApiName"];
                 options.SupportedTokens = SupportedTokens.Both;
                 options.SaveToken = false;
                 options.EnableCaching = false; 
                 options.CacheDuration = TimeSpan.FromMinutes(10);
             );

    services.AddAuthorization(options =>
        options.AddPolicy("protectedScope", policy =>
        
            policy.AuthenticationSchemes = new List<string>  DEFAULT_AUTH_SCHEME ;
            policy.RequireAuthenticatedUser();
            policy.RequireClaim("someclaim");
        ));

AuthorizationHeaderMiddleware.cs:

    public class AuthorizationHeaderMiddleware
    
        private RequestDelegate _next;
        private readonly IConfiguration _configuration;   

        public AuthorizationHeaderMiddleware(RequestDelegate next, IConfiguration configuration)
        
            _configuration = configuration;
            _next = next;
        

        public async Task Invoke(HttpContext context)
        
            // here I intend to get user from the (backend) DB based on "sub" claim from IdentityServer's token and set users claims from DB. Is this correct attitude?
            var claims = new List<Claim>  new Claim("someclaim", "aaaa") ;
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Authorization:JwtKey"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken(
                _configuration["Authorization:JwtIssuer"],
                _configuration["Authorization:JwtIssuer"],
                claims,
                signingCredentials: creds
            );

            var tokenGenerated = new JwtSecurityTokenHandler().WriteToken(token);
            context.Request.Headers["Authorization"] = $"DEFAULT_AUTH_SCHEME tokenGenerated";

            await _next.Invoke(context);
        

TestController.cs

    [Authorize(Policy = "protectedScope", AuthenticationSchemes = DEFAULT_AUTH_SCHEME)]
    public class TestController
    
        [HttpGet]
        public IActionResult TestAction()
        
            return Ok();
        
    

如果我在 TestController 中请求测试操作,我会得到 401 Unauthorized。

我在这里做错了什么?

这不是Use multiple JWT Bearer Authentication 问题的重复,因为我已经尝试过这个答案,但没有成功。此外,这是另一种情况,因为我想使用 IdentityServer 进行身份验证,使用后端 JWT 进行授权。

【问题讨论】:

你能详细说明一下,它是IS的令牌是什么意思? Use multiple JWT Bearer Authentication的可能重复 @Div : "如果标头包含由 IS 生成的令牌" 【参考方案1】:

您只能使用IdentityServerAuthentication 并在成功验证后将应用程序用户的声明添加到当前用户。你可以通过使用 OnTokenValidated 来做到这一点

services.AddAuthentication()
        .AddIdentityServerAuthentication(DEFAULT_AUTH_SCHEME, options =>
        
            options.Authority = Configuration["IdentityServer:Url"];
            options.RequireHttpsMetadata = false;
            options.ApiName = Configuration["IdentityServer:ApiName"];
            options.SupportedTokens = SupportedTokens.Both;
            options.SaveToken = false;
            options.EnableCaching = false;
            options.CacheDuration = TimeSpan.FromMinutes(10);
            options.JwtBearerEvents.OnTokenValidated = async context =>
            
                // get subject from authenticated principal
                var subject = context.Principal.FindFirst("sub");

                // get claims from your database for the subject
                var claims = new List<Claim>  new Claim("someclaim", "aaaa") ;

                // change the principal
                context.Principal = new System.Security.Claims.ClaimsPrincipal(claims);
            ;
        );

【讨论】:

OnTokenValidated 方法需要等待,因为“异步”。同样在 .net Core 上,最后两行不正确,我将它们改写为:((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim("someclaim", "whatever string"));

以上是关于用于授权和身份验证的多个 JWT 承载的主要内容,如果未能解决你的问题,请参考以下文章

Auth0 - 在 Owin 上使用带有承载访问令牌的 JWT 使用 RS256 进行身份验证

在 ASP.Net 应用程序上结合 ADFS 身份验证和 JWT 承载

使用 JWT 的承载方案的身份验证标头

.Net SignalR 在还配置了 Cookie 身份验证时使用 JWT 承载身份验证

Swashbuckle .NET Core 2 中 JWT 承载的授权

将 JWT 用于匿名和经过身份验证的用户