Asp.Net Core 中的多个 JWT 权限/发行者

Chr*_*att 8 jwt bearer-token asp.net-core identityserver4 ocelot

我正在尝试使用 Ocelot 在 ASP.Net API 网关中获得 JWT 不记名身份验证以与多个机构/发行者合作。一个颁发者是 Auth0,另一个是基于 IdentityServer4 的内部认证服务器;我们正在尝试从 Auth0 迁移,但仍有依赖它的外部客户端,因此我们希望支持两者,直到所有内容都经过全面测试以进行切换。

根据this MSDN blog post,应该可以通过设置TokenValidationParameters.ValidIssuers而不是使用多个权限JwtBearerOptions.Authority。但是,无论TokenValidationParameters.ValidIssuers.

有谁知道如何让这个工作?这就是我设置身份验证的方式。它仅在注释行未注释时才有效(并且仅适用于由该单一机构颁发的令牌)。我期待 Ocelot 或 ASP.Net Core 从发行服务器获取密钥;两者都通过 .well-known/openid-configuration 提供 JWK,它与 ASP.Net Core 中间件一起使用。

public static void AddJwtBearerAuthentication(this IServiceCollection services, IConfiguration configuration)
{
    services
        .AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
        {
            //options.Authority = configuration["Jwt:Authority"];
            options.Audience  = configuration["Jwt:Audience"];
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer           = true,
                ValidateIssuerSigningKey = true,
                ValidateAudience         = true,
                ValidAudience            = configuration["Jwt:Audience"],
                ValidIssuers             = configuration
                    .GetSection("Jwt:Authorities")
                    .AsEnumerable()
                    .Select(kv => kv.Value)
                    .Where(s => !string.IsNullOrEmpty(s))
                    .ToArray()
            };
        });
}
Run Code Online (Sandbox Code Playgroud)

当客户端错误的发行者(或当我们使用TokenValidationParameters.ValidIssuer/ 时ValidIssuers)连接时,Ocelot 的输出是:

[16:35:37 WRN] requestId: _____, previousRequestId: no previous request id, message: Error Code: UnauthenticatedError Message: Request for authenticated route _____ by  was unauthenticated errors found in ResponderMiddleware. Setting error response for request path:_____, request method: POST
Run Code Online (Sandbox Code Playgroud)

这是 client_credentials 身份验证,因此在“by”之后缺少用户名。如您所见,Ocelot 没有说明确切的问题是什么。ASP.Net Core JWT 不记名中间件(没有 Ocelot)只是说签名无效。我怀疑它要么没有在看TokenValidationParameters,要么我误解了它们的目的。

Chr*_*att 15

我想出了如何做到这一点:

  1. 使用services.AddAuthentication(). 如果需要,您可以设置默认方案(为“Bearer”),但这不是必需的。

  2. 添加authenticationBuilder.AddJwtBearer()任意多个不同的 JWT Bearer 配置,每个配置都有自己的密钥(例如“Auth0”、“IS4”……)。我在 appsettings.json 中的数组上使用了循环

  3. 创建一个策略方案,authenticationBuilder.AddPolicyScheme并为其指定方案名称“Bearer”(用于JwtBearerDefaults.AuthenticationScheme避免在您的代码中使用魔术字符串)并options.ForwardDefaultSelector在回调中设置为返回其他方案名称之一的函数(“Auth0”、“IS4”或无论你放什么)取决于某些标准。在我的情况下,它只是在 JWT 颁发者中查找方案名称(如果颁发者包含“auth0”,则使用 Auth0 方案)。

代码:

public static void AddMultiSchemeJwtBearerAuthentication(
    this IServiceCollection services,
    IConfiguration configuration
)
{
    // Create JWT Bearer schemes.
    var schemes = configuration
        .GetSection("Jwt")
        .GetChildren()
        .Select(s => s.Key)
        .ToList()
    ;
    var authenticationBuilder = services.AddAuthentication();
    foreach (var scheme in schemes)
    {
        authenticationBuilder.AddJwtBearer(scheme, options =>
        {
            options.Audience  = configuration[$"Jwt:{scheme}:Audience"];
            options.Authority = configuration[$"Jwt:{scheme}:Authority"];
        });
    }

    // Add scheme selector.
    authenticationBuilder.AddPolicyScheme(
        JwtBearerDefaults.AuthenticationScheme,
        "Selector",
        options =>
        {
            options.ForwardDefaultSelector = context =>
            {
                // Find the first authentication header with a JWT Bearer token whose issuer
                // contains one of the scheme names and return the found scheme name.
                var authHeaderNames = new[] {
                    HeaderNames.Authorization,
                    HeaderNames.WWWAuthenticate
                };
                StringValues headers;
                foreach (var headerName in authHeaderNames)
                {
                    if (context.Request.Headers.TryGetValue(headerName, out headers) && !StringValues.IsNullOrEmpty(headers))
                    {
                        break;
                    }
                }

                if (StringValues.IsNullOrEmpty(headers))
                {
                    // Handle error. You can set context.Response.StatusCode and write a
                    // response body. Returning null invokes default scheme which will raise
                    // an exception; not sure how to fix this so the request is rejected.
                    return null;
                }

                foreach (var header in headers)
                {
                    var encodedToken = header.Substring(JwtBearerDefaults.AuthenticationScheme.Length + 1);
                    var jwtHandler = new JwtSecurityTokenHandler();
                    var decodedToken = jwtHandler.ReadJwtToken(encodedToken);
                    var issuer = decodedToken?.Issuer?.ToLower();
                    foreach (var scheme in schemes)
                    {
                        if (issuer?.Contains(scheme.ToLower()) == true)
                        {
                            // Found the scheme.
                            return scheme;
                        }
                    }
                }
                // Handle error.
                return null;
            };
        }
    );
}
Run Code Online (Sandbox Code Playgroud)

让 Ocelot 支持这一点不需要什么特别的东西,只需使用“Bearer”作为身份验证提供者密钥,方案选择器策略将被自动调用。


Hir*_*ran 7

.net 5 的工作解决方案。

  • 这适用于多个 JWT 不记名令牌发行者

  • 默认模式将路由到相应的模式

    // Get list of domains and audience from the config
         var authorities = Configuration["Auth:Domain"].Split(',').Distinct().ToList();
         var audience = Configuration["Auth:Audience"];
         // Add default empty schema schema selection policy
         var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
             options =>
             {
                 // forward to corresponding schema based on token's issuer 
                 // this will read the token and check the token issues , if the token issuer is registered in config then redirect to that schema
                 options.ForwardDefaultSelector = context =>
                 {
                     string authorization = context.Request.Headers[HeaderNames.Authorization];
    
                     if (!string.IsNullOrEmpty(authorization))
                     {
                         if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                         {
                             var token = authorization.Substring("Bearer ".Length).Trim();
    
                             var jwtHandler = new JwtSecurityTokenHandler();
                             if (jwtHandler.CanReadToken(token))
                             {
                                 var jwtToken = jwtHandler.ReadJwtToken(token);
                                 if (authorities.Contains(jwtToken.Issuer))
                                     return jwtToken.Issuer;
                             }
                         }
                     }
                     return null;
                 };
             });
    
         // Register all configured schemas 
         foreach (var auth in authorities)
         {
             authenticationBuilder.AddJwtBearer(auth, options =>
             {
    
                 options.SaveToken = true;
                 options.Audience = audience;
                 options.Authority = auth;
                 options.TokenValidationParameters = new TokenValidationParameters
                 {
                     NameClaimType = "sub",
                     ValidateIssuer = true,
                     ValidateAudience = true,
                     ValidateLifetime = true,
                     RequireSignedTokens = true,
                     ValidateIssuerSigningKey = true
                 };
             });
    
         }
    
    Run Code Online (Sandbox Code Playgroud)


Sat*_*ies 6

这是工作示例:

public void ConfigureServices(IServiceCollection services)
{
   services.AddAuthentication(options => 
   {
       options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
   })
    //set default authentication 
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        //set the next authentication configuration to be used
        options.ForwardDefaultSelector = ctx => "idp4";

        //...rest of the options goes here
        };
    })
    .AddJwtBearer("idp4", options => 
     {
        //set the next authentication configuration to be used
        options.ForwardDefaultSelector = ctx => "okta";
        //options goes here
     })
    .AddJwtBearer("okta", options => 
     {
        //options goes here
     });
Run Code Online (Sandbox Code Playgroud)