前言
上次,我们实现了《ASP.NET Core 自定义认证》:.
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = DemoAuthenticationOptions.Scheme;
option.DefaultChallengeScheme = DemoAuthenticationOptions.Scheme;
})
.AddDemoAuthentication(options => { });
我们希望同时也能支持 JWT 认证,于是又加上了AddJwtBearer
:
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = DemoAuthenticationOptions.Scheme;
option.DefaultChallengeScheme = DemoAuthenticationOptions.Scheme;
})
.AddDemoAuthentication(options => { })
.AddJwtBearer(options =>
{
...
});
但是,我们发现 JWT 认证始终不起作用,只能使用自定义认证。
猜测可能是设置了DefaultAuthenticateScheme
的原因,于是去掉了相关代码:
services.AddAuthentication()
.AddDemoAuthentication(options => { })
.AddJwtBearer(options =>
{
...
});
这次问题更大,直接报错了:
查找原因
根据错误提示,我们找到了AuthenticationService
的源码:
public virtual async Task ChallengeAsync(HttpContext context, string? scheme, AuthenticationProperties? properties)
{
if (scheme == null)
{
var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
scheme = defaultChallengeScheme?.Name;
if (scheme == null)
{
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
}
如果没有传入scheme
,则使用DefaultChallengeScheme
。
继续向上查找AuthorizationMiddlewareResultHandler
的源码:
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if (authorizeResult.Challenged)
{
if (policy.AuthenticationSchemes.Count > 0)
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(scheme);
}
}
scheme
来自于policy.AuthenticationSchemes
。
继续向上查找AuthorizationMiddleware
的源码:
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
policy
来自于IAuthorizeData
的实现类。
那么,IAuthorizeData
的实现类是谁呢:
public class AuthorizeAttribute : Attribute, IAuthorizeData
解决问题
现在,我们只需要在AuthorizeAttribute
设置AuthenticationSchemes
属性即可:
[HttpGet]
[Authorize(AuthenticationSchemes = "Bearer,Demo")]
public string Get()
{
...
}
Bearer
和Demo
分别是JwtBearerHandler
和DemoAuthenticationHandler
的默认 scheme。
结论
因为可以为不同方法设置AuthorizeAttribute
,因此,完全可以设置特定路由只能接受指定的认证方式:
[HttpGet("OnlyForBearer")]
[Authorize(AuthenticationSchemes = "Bearer")]
public string OnlyForBearer()
{
...
}
[HttpGet("OnlyForDemo")]
[Authorize(AuthenticationSchemes = "Demo")]
public string OnlyForDemo()
{
...
}