使用多个JWT承载身份验证


86

是否可以在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
                };
        }

我可以有多个发行人和受众,但不能设置多个授权机构。


1
可以将任意数量的属性添加到JWT。因此,没有什么可以阻止您在JWT中记录两个发行者名称。问题来了,如果每个发行者都使用不同的密钥进行签名,那么您的应用程序将需要同时知道两个密钥。
Tim Biegeleisen '18

Answers:


185

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

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身份验证方案都应尝试验证该请求来更改授权系统的默认策略。

但这并不能阻止您对某些操作施加更多限制;该[Authorize]属性具有AuthenticationSchemes允许您覆盖哪些身份验证方案有效的属性。

如果您有更复杂的方案,则可以使用基于策略的授权。我发现官方文档很棒。

假设某些操作仅适用于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依次检查每个事件直到获得匹配项引起的。该错误通常可以忽略。


4
这是否要求从Firebase或自定义解决方案更改标头值?即。而不是Authorization : Bearer <token>标题Authorization : Firebase <token>例如?尝试此解决方案时,出现错误:“没有为方案'Bearer'注册任何身份验证处理程序。”
Rush Frisby

4
不,标题不需要更改。该错误消息表明您引用的是不存在的身份验证方案(Bearer)。在我们的示例中,两个注册的方案是Firebase和Custom,它们是.AddJwtBearer方法调用的第一个参数。
米凯尔·德瑞(MickaëlDerriey)

5
你好 只是在寻找这种解决方案。不幸的是,我收到“未指定authenticationScheme,并且没有找到DefaultChallengeScheme”异常。options.DefaultPolicy设置为OK。有任何想法吗?
terjetyl

11
这是一个非常有用的答案,将我在各处看到的很多东西汇总在一起。
Aron W.

2
@TylerOhlsen不正确;虽然将在您描述的情况下使用它,但它不是唯一的一种。如果您没有在端点级别指定授权要求,而是用空[Authorize]属性修饰MVC控制器和/或动作,则也将使用它。
的MickaëlDerriey

3

这是MickaëlDerriey答案的延伸。

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

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());
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.