在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等。在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现代化的需求,因此在ASP.NET Core 中对认证及授权进行了全新设计,使其更加灵活,可以应付各种场景。在上一章中,我们提到HttpContext中认证相关的功能放在了独立的模块中,以扩展的方式来展现,以保证HttpContext的简洁性,本章就来介绍一下 ASP.NET Core 认证系统的整个轮廓,以及它的切入点。
AuthenticationHttpContextExtensions
AuthenticationHttpContextExtensions 类是对 HttpContext 认证相关的扩展,它提供了如下扩展方法:
public static class AuthenticationHttpContextExtensions{
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme); public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { } public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { } public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) {} public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { } public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) { }
}
主要包括如上6个扩展方法,其它的只是一些参数重载:
SignInAsync 用户登录成功后颁发一个证书(加密的用户凭证),用来标识用户的身份。
SignOutAsync 退出登录,如清除Coookie等。
AuthenticateAsync 验证在
SignInAsync
中颁发的证书,并返回一个AuthenticateResult
对象,表示用户的身份。ChallengeAsync 返回一个需要认证的标识来提示用户登录,通常会返回一个
401
状态码。ForbidAsync 禁上访问,表示用户权限不足,通常会返回一个
403
状态码。GetTokenAsync 用来获取
AuthenticationProperties
中保存的额外信息。
它们的实现都非常简单,与展示的第一个方法类似,从DI系统中获取到 IAuthenticationService
接口实例,然后调用其同名方法。
因此,如果我们希望使用认证服务,那么首先要注册 IAuthenticationService
的实例,ASP.NET Core 中也提供了对应注册扩展方法:
public static class AuthenticationCoreServiceCollectionExtensions{ public static IServiceCollection AddAuthenticationCore(this IServiceCollection services) {
services.TryAddScoped<IAuthenticationService, AuthenticationService>();
services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
return services;
}
public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) {
services.AddAuthenticationCore();
services.Configure(configureOptions);
return services;
}
}
如上,AddAuthenticationCore 中注册了认证系统的三大核心对象:IAuthenticationSchemeProvider
,IAuthenticationHandlerProvider
和 IAuthenticationService
,以及一个对Claim进行转换的 IClaimsTransformation(不常用),下面就来介绍一下这三大对象。
IAuthenticationSchemeProvider
首先来解释一下 Scheme 是用来做什么的。因为在 ASP.NET Core 中可以支持各种各样的认证方式(如,cookie, bearer, oauth, openid 等等),而 Scheme 用来标识使用的是哪种认证方式,不同的认证方式其处理方式是完全不一样的,所以Scheme是非常重要的。
IAuthenticationSchemeProvider 用来提供对Scheme的注册和查询,定义如下:
public interface IAuthenticationSchemeProvider{
void AddScheme(AuthenticationScheme scheme);
Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
Task<AuthenticationScheme> GetSchemeAsync(string name);
Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();
Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync(); Task<AuthenticationScheme> GetDefaultForbidSchemeAsync(); Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();
Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();
}
其 AddScheme
方法,用来注册Scheme,而每一种Scheme最终体现为一个 AuthenticationScheme
类型的对象:
public class AuthenticationScheme{
public AuthenticationScheme(string name, string displayName, Type handlerType) {
if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
{
throw new ArgumentException("handlerType must implement IAuthenticationSchemeHandler.");
}
...
} public string Name { get; } public string DisplayName { get; } public Type HandlerType { get; }
}
每一个Scheme中还包含一个对应的IAuthenticationHandler
类型的Handler,由它来完成具体的处理逻辑,看一下它的默认实现:
public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider{
private IDictionary<string, AuthenticationScheme> _map = new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal);
public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options) {
_options = options.Value;
foreach (var builder in _options.Schemes)
{
var scheme = builder.Build();
AddScheme(scheme);
}
}
private Task<AuthenticationScheme> GetDefaultSchemeAsync() => _options.DefaultScheme != null
? GetSchemeAsync(_options.DefaultScheme)
: Task.FromResult<AuthenticationScheme>(null);
....
}
如上,通过一个内部的字典来保存我们所注册的Scheme,key为Scheme名称,然后提供一系列对该字典的查询。它还提供了一系列的GetDefaultXXXSchemeAsync
方法,所使用的Key是通过构造函数中接收的AuthenticationOptions
对象来获取的,如果未配置,则返回为