是否可以在 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
};
}
我可以有多个发行者和受众,但我不能设置多个权威。
你可以完全实现你想要的:
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
。
由于这不是我们想要的,因为我们想让身份验证处理程序有机会对请求进行身份验证,所以我们通过指示 Firebase
和 Custom
身份验证方案都应尝试<来更改授权系统的默认策略/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
直到它得到匹配。该错误通常可以忽略。
这是 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());
});
您的问题的解决方案可在以下博客文章 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;
}
}
}
Mickael 的回答中缺少的一件事是需要在 Authorize 属性中指定方案(如果您想使用授权)
[授权(AuthenticationSchemes =“Firebase,自定义”,Policy =“FirebaseAdministrators”)]
如果没有提供 AuthenticationSchemes,并且 AddAuthentication() 没有参数,NetCore 无法进行身份验证并且 Request.HttpContext.User.Identity.IsAuthenticated 设置为 false
Authorization : Bearer <token>
标头是Authorization : Firebase <token>
?尝试此解决方案时,我收到错误消息:“没有为方案 'Bearer' 注册身份验证处理程序。”.AddJwtBearer
方法调用的第一个参数。[Authorize]
属性装饰 MVC 控制器和/或操作,也将使用它。