如上,通过一个内部的字典来保存我们所注册的Scheme,key为Scheme名称,然后提供一系列对该字典的查询。它还提供了一系列的GetDefaultXXXSchemeAsync方法,所使用的Key是通过构造函数中接收的AuthenticationOptions对象来获取的,如果未配置,则返回为null。
对于 AuthenticationOptions 对象,大家可能会比较熟悉,在上面介绍的 AddAuthenticationCore 扩展方法中,也是使用该对象来配置认证系统:
public class AuthenticationOptions { private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>(); public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes; public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = new Dictionary<string, AuthenticationSchemeBuilder>(StringComparer.Ordinal); public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder) { if (SchemeMap.ContainsKey(name)) { throw new InvalidOperationException("Scheme already exists: " + name); } var builder = new AuthenticationSchemeBuilder(name); configureBuilder(builder); _schemes.Add(builder); SchemeMap[name] = builder; } public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler => AddScheme(name, b => { b.DisplayName = displayName; b.HandlerType = typeof(THandler); }); public string DefaultScheme { get; set; } public string DefaultAuthenticateScheme { get; set; } public string DefaultSignInScheme { get; set; } public string DefaultSignOutScheme { get; set; } public string DefaultChallengeScheme { get; set; } public string DefaultForbidScheme { get; set; } }该对象可以帮助我们更加方便的注册Scheme,提供泛型和 AuthenticationSchemeBuilder 两种方式配置方式。
到此,我们了解到,要想使用认证系统,必要先注册Scheme,而每一个Scheme必须指定一个Handler,否则会抛出异常,下面我们就来了解一下Handler。
IAuthenticationHandlerProvider在 ASP.NET Core 的认证系统中,AuthenticationHandler 负责对用户凭证的验证,它定义了如下接口:
public interface IAuthenticationHandler { Task InitializeAsync(AuthenticationScheme scheme, HttpContext context); Task<AuthenticateResult> AuthenticateAsync(); Task ChallengeAsync(AuthenticationProperties properties); Task ForbidAsync(AuthenticationProperties properties); }AuthenticationHandler的创建是通过 IAuthenticationHandlerProvider 来完成的:
public interface IAuthenticationHandlerProvider { Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme); }Provider 只定义了一个 GetHandlerAsync 方法,来获取指定的Scheme的Hander,在 ASP.NET Core 中,很多地方都使用了类似的 Provider 模式。
而HandlerProvider的实现,我们通过对上面SchemeProvider的了解,应该可以猜到一二,因为在 AuthenticationScheme 中已经包含了Hander:
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider { public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes) { Schemes = schemes; } public IAuthenticationSchemeProvider Schemes { get; } private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal); public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme) { if (_handlerMap.ContainsKey(authenticationScheme)) { return _handlerMap[authenticationScheme]; } var scheme = await Schemes.GetSchemeAsync(authenticationScheme); if (scheme == null) { return null; } var handler = (context.RequestServices.GetService(scheme.HandlerType) ?? ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType)) as IAuthenticationHandler; if (handler != null) { await handler.InitializeAsync(scheme, context); _handlerMap[authenticationScheme] = handler; } return handler; } }可以看到,AuthenticationHandlerProvider 首先使用 IAuthenticationSchemeProvider 获取到当前Scheme,然后先从DI中查找是否有此Scheme中的Handler,如果未注册到DI系统中,则使用 ActivatorUtilities 来创建其实例,并缓存到内部的 _handlerMap 字典中。
IAuthenticationServiceIAuthenticationService 本质上是对 IAuthenticationSchemeProvider 和 IAuthenticationHandlerProvider 封装,用来对外提供一个统一的认证服务接口:
public interface IAuthenticationService { Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme); Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties); Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties); Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties); Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties); }这5个方法中,都需要接收一个 scheme 参数,因为只有先指定你要使用的认证方式,才能知道该如何进行认证。