ChatGPT解决这个技术问题 Extra ChatGPT

是否可以在 ASP.NET Core 2 中支持多个 JWT 令牌颁发者?我想为外部服务提供 API,我需要使用两个 JWT 令牌来源 - Firebase 和自定义 JWT 令牌颁发者。在 ASP.NET 核心中,我可以为 Bearer 身份验证方案设置 JWT 身份验证,但仅限于一个权限:

  services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = "https://securetoken.google.com/my-firebase-project"
            options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = "my-firebase-project"
                    ValidateAudience = true,
                    ValidAudience = "my-firebase-project"
                    ValidateLifetime = true
                };
        }

我可以有多个发行者和受众,但我不能设置多个权威。

AFAIK 您可以向 JWT 添加任意数量的属性。因此,没有什么能阻止您在 JWT 中记录两个颁发者名称。问题在于,如果每个颁发者使用不同的密钥进行签名,您的应用程序将需要知道这两个密钥。

m
mrapi

你可以完全实现你想要的:

services
    .AddAuthentication()
    .AddJwtBearer("Firebase", options =>
    {
        options.Authority = "https://securetoken.google.com/my-firebase-project"
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "my-firebase-project"
            ValidateAudience = true,
            ValidAudience = "my-firebase-project"
            ValidateLifetime = true
        };
    })
    .AddJwtBearer("Custom", options =>
    {
        // Configuration for your custom
        // JWT tokens here
    });

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();
    });

让我们来看看你的代码和那个代码之间的区别。

AddAuthentication 没有参数

如果您设置了默认身份验证方案,那么在每个请求中,身份验证中间件都将尝试运行与默认身份验证方案关联的身份验证处理程序。由于我们现在有两种可能的身份验证方案,因此运行其中一种是没有意义的。

使用 AddJwtBearer 的另一个重载

添加身份验证的每个 AddXXX 方法都有几个重载:

一种使用与身份验证方法关联的默认身份验证方案,如您在此处看到的 cookie 身份验证

除了选项的配置之外,您还可以传递身份验证方案的名称,就像在这个重载中一样

现在,因为您使用相同的身份验证方法两次,但身份验证方案必须是唯一的,您需要使用第二个重载。

更新默认策略

由于不再自动对请求进行身份验证,因此将 [Authorize] 属性放在某些操作上将导致请求被拒绝并发出 HTTP 401

由于这不是我们想要的,因为我们想让身份验证处理程序有机会对请求进行身份验证,所以我们通过指示 FirebaseCustom 身份验证方案都应尝试<来更改授权系统的默认策略/em> 来验证请求。

这并不妨碍您对某些操作更加严格; [Authorize] 属性有一个 AuthenticationSchemes 属性,允许您覆盖哪些身份验证方案有效。

如果您有更复杂的场景,您可以使用 policy-based authorization。我发现官方文档很棒。

假设某些操作仅适用于 Firebase 发布的 JWT 令牌,并且必须具有特定值的声明;你可以这样做:

// Authentication code omitted for brevity

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();

        options.AddPolicy("FirebaseAdministrators", new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase")
            .RequireClaim("role", "admin")
            .Build());
    });

然后您可以在某些操作上使用 [Authorize(Policy = "FirebaseAdministrators")]

最后要注意的一点:如果您正在捕获 AuthenticationFailed 事件并使用第一个 AddJwtBearer 策略以外的任何策略,您可能会看到 IDX10501: Signature validation failed. Unable to match key... 这是由于系统依次检查每个 AddJwtBearer 直到它得到匹配。该错误通常可以忽略。


这是否需要从 firebase 或自定义解决方案更改标头值? IE。例如,而不是 Authorization : Bearer <token> 标头是 Authorization : Firebase <token>?尝试此解决方案时,我收到错误消息:“没有为方案 'Bearer' 注册身份验证处理程序。”
不,标题不需要更改。错误消息表明您指的是不存在的身份验证方案(承载)。在我们的示例中,两个已注册的方案是 Firebase 和 Custom,它们是 .AddJwtBearer 方法调用的第一个参数。
你好。正在寻找这个解决方案。不幸的是,我收到“未指定身份验证方案,并且未找到 DefaultChallengeScheme”异常。 options.DefaultPolicy 设置正常。有任何想法吗?
这是一个非常有帮助的答案,并将我在各地看到的很多东西拼凑在一起。
@TylerOhlsen 这不正确;虽然它将在您描述的情况下使用,但它不是唯一的。如果您未在端点级别指定授权要求,但使用空的 [Authorize] 属性装饰 MVC 控制器和/或操作,也将使用它。
N
No Refunds No Returns

这是 Mickaël Derriey 答案的延伸。

我们的应用程序有一个自定义授权要求,我们从内部来源解决。我们使用的是 Auth0,但正在使用 OpenID 切换到 Microsoft 帐户身份验证。这是我们的 ASP.Net Core 2.1 Startup 中经过稍微编辑的代码。对于未来的读者,这在撰写本文时适用于指定的版本。调用者在传入请求中使用来自 OpenID 的 id_token 作为承载令牌传递。希望它可以帮助其他人尝试进行身份权限转换,就像这个问题和答案对我有帮助一样。

const string Auth0 = nameof(Auth0);
const string MsaOpenId = nameof(MsaOpenId);

string domain = "https://myAuth0App.auth0.com/";
services.AddAuthentication()
        .AddJwtBearer(Auth0, options =>
            {
                options.Authority = domain;
                options.Audience = "https://myAuth0Audience.com";
            })
        .AddJwtBearer(MsaOpenId, options =>
            {
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateAudience = true,
                    ValidAudience = "00000000-0000-0000-0000-000000000000",

                    ValidateIssuer = true,
                    ValidIssuer = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",

                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                    RequireSignedTokens = true,
                    ClockSkew = TimeSpan.FromMinutes(10),
                };
                options.MetadataAddress = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0/.well-known/openid-configuration";
            }
        );

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes( Auth0, MsaOpenId )
        .Build();

    var approvedPolicyBuilder =  new AuthorizationPolicyBuilder()
           .RequireAuthenticatedUser()
           .AddAuthenticationSchemes(Auth0, MsaOpenId)
           ;

    approvedPolicyBuilder.Requirements.Add(new HasApprovedRequirement(domain));

    options.AddPolicy("approved", approvedPolicyBuilder.Build());
});

P
Pieter

您的问题的解决方案可在以下博客文章 https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme 中找到

基本上,存在使用您自己的通用处理程序覆盖常规 JWTBearer 处理程序的解决方案,该处理程序可以通过 JWTBearerConfig 检查 cfg 中的颁发者是否与您的令牌中的 isseur 相同。

博客文章建议为每个方案使用单独的处理程序,这似乎不需要,覆盖 HandleAuthenticateAsync 方法的通用类 JWTAuthenticationHandler 似乎就足够了!

明智的代码你可以像这样实现你的启动:

 //Using multiple schemes can cause issues when validating the issuesSigningKey therefore we need to implement seperate handlers for each scheme! => cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
        services.AddAuthentication()
        //Set the authenticationScheme by using the identityServer helper methods (we are using a Bearer token)
        .AddScheme<JwtBearerOptions, JWTAuthenticationHandler>(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
        {
            //TO DO Get the origin url's from configuration file, instead of setting all url's here 
            options.Authority = _identityServerSettings.Authority;
            options.Audience = _identityServerSettings.Audience;

            options.Events = new JwtBearerEvents
            {
                OnChallenge = context =>
                {
                    return Task.CompletedTask;
                },
                //When using multiple JwtBearer schemes we can run into "OnAuthenticationFailed" for instance when logging in via IdentityServer the AuthenticationHandler will still check in these events, this can be ignored...
                //Cfr => https://stackoverflow.com/questions/49694383/use-multiple-jwt-bearer-authentication
                //If you are catching AuthenticationFailed events and using anything but the first AddJwtBearer policy, you may see IDX10501: Signature validation failed.Unable to match key... This is caused by the system checking each AddJwtBearer in turn until it gets a match. The error can usually be ignored.
                //We managed to fix this issue by adding seperate AuthenticationHandlers for each type of bearer token... cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
                OnAuthenticationFailed = context =>
                {
                    return Task.CompletedTask;
                },
                OnMessageReceived = context =>
                {
                    return Task.CompletedTask;
                },
                OnForbidden = context =>
                {
                    return Task.CompletedTask;
                },
                OnTokenValidated = context =>
                {
                    return Task.CompletedTask;
                }

            };
        })
        //Set the authentication scheme for the AzureAd integration (we are using a bearer token)
        .AddScheme<JwtBearerOptions, JWTAuthenticationHandler>("AzureAD", "AzureAD", options =>
         {
            options.Audience = _azureAdSettings.Audience;   //ClientId 
            options.Authority = _azureAdSettings.Authority; //"https://login.microsoftonline.com/{tenantId}/v2.0/"

            options.TokenValidationParameters = new TokenValidationParameters
             {
                //Set built in claimTypes => Role
                RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
             };

             options.Events = new JwtBearerEvents
             {
                 OnChallenge = context =>
                 {
                     return Task.CompletedTask;
                 },
                 //When using multiple JwtBearer schemes we can run into "OnAuthenticationFailed" for instance when logging in via IdentityServer the AuthenticationHandler will still check in these events, this can be ignored...
                 //Cfr => https://stackoverflow.com/questions/49694383/use-multiple-jwt-bearer-authentication
                 //A final point to note: If you are catching AuthenticationFailed events and using anything but the first AddJwtBearer policy, you may see IDX10501: Signature validation failed.Unable to match key... This is caused by the system checking each AddJwtBearer in turn until it gets a match. The error can usually be ignored.
                 //We managed to fix this issue by adding seperate AuthenticationHandlers for each type of bearer token... cfr: https://oliviervaillancourt.com/posts/Fixing-IDX10501-MultipleAuthScheme
                 OnAuthenticationFailed = context =>
                 {
                     return Task.CompletedTask;
                 },
                 OnMessageReceived = context =>
                 {
                     return Task.CompletedTask;
                 },
                 OnForbidden = context =>
                 {
                     return Task.CompletedTask;
                 },
                 OnTokenValidated = context =>
                 {
                     return Task.CompletedTask;
                 }
                 
             };
         });
    }

JWTAuthenticationHandlerClass 看起来像这样

  using Microsoft.AspNetCore.Authentication;
  using Microsoft.AspNetCore.Authentication.JwtBearer;
  using Microsoft.Extensions.Logging;
  using Microsoft.Extensions.Options;
  using System;
  using System.IdentityModel.Tokens.Jwt;
  using System.Text.Encodings.Web;
  using System.Threading.Tasks;

  namespace WebAPI.Auth
  {
    public class JWTAuthenticationHandler: JwtBearerHandler
    {
    public JWTAuthenticationHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        //Fetch OIDC configuration for the IDP we are handling
        var authorityConfig = await this.Options.ConfigurationManager.GetConfigurationAsync(this.Context.RequestAborted);
        //Determine the issuer from the configuration
        var authorityIssuer = authorityConfig.Issuer;

        var jwtToken = this.ReadTokenFromHeader();
        var jwtHandler = new JwtSecurityTokenHandler();

        //Check if we can read the token as a valid JWT, if not let the JwtBearerHandler do it's thing...
        if (jwtHandler.CanReadToken(jwtToken))
        {
            //Read the token and determine if the issuer in config is the same as the one in the token, if this is true we know we want to let the JwtBearerHandler continue, if not we skip and return noResult
            //This way the next IDP configuration will pass here until we find a matching issuer and then we know that is the IDP we are dealing with
            var token = jwtHandler.ReadJwtToken(jwtToken);
            if (string.Equals(token.Issuer, authorityIssuer, StringComparison.OrdinalIgnoreCase))
            {
                return await base.HandleAuthenticateAsync();
            }
            else
            {
                // return NoResult since the issuer in cfg did not match the one in the token, so no need to proceed to tokenValidation
                this.Logger.LogDebug($"Skipping jwt token validation because token issuer was {token.Issuer} but the authority issuer is: {authorityIssuer}");
                return AuthenticateResult.NoResult();
            }
        }

        return await base.HandleAuthenticateAsync();
    }

    //Fetch the bearer token from the authorization header on the request!
    private string ReadTokenFromHeader()
    {
        string token = null;

        string authorization = Request.Headers["Authorization"];

        //If we don't find the authorization header return null
        if (string.IsNullOrEmpty(authorization))
        {
            return null;
        }

        //get the token from the auth header
        if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
        {
            token = authorization.Substring("Bearer ".Length).Trim();
        }

        return token;
    }
}

}


h
hurray yarruh

Mickael 的回答中缺少的一件事是需要在 Authorize 属性中指定方案(如果您想使用授权)

[授权(AuthenticationSchemes =“Firebase,自定义”,Policy =“FirebaseAdministrators”)]

如果没有提供 AuthenticationSchemes,并且 AddAuthentication() 没有参数,NetCore 无法进行身份验证并且 Request.HttpContext.User.Identity.IsAuthenticated 设置为 false


您的答案可以通过额外的支持信息得到改进。请edit添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。您可以找到有关如何写出好答案的更多信息in the help center

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅