【ASP.NET Core 认证】IAuthenticationSchemeProvider、IAuthenticationHandlerProvider、IAuthenticationService
AuthenticationHttpContextExtensions
AuthenticationHttpContextExtensions
类是对 HttpContext
认证相关的扩展,在Core2.0中是使用HttpContext的Authentication属性做认证
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) { }
//用来获取 AuthenticationProperties 中保存的额外信息
public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) { }
}
- AuthenticateAsync:从请求中获取上面发放的身份令牌,然后解析成
AuthenticationTicket
,经过验证,最终返回AuthenticateResult
对象。比如cookie认证方案,校验是否有包含cookie;jwtbearer认证方案校验是否有Authorization请求头 - ChallengeAsync:返回一个需要认证的标识来提示用户登录,比如cookie认证方案,跳转
/login
;jwtbearer认证方案返回一个 401 状态码 - ForbidAsync:禁止访问,表示用户权限不足,通常会返回一个 403 状态码
- SignInAsync:用户登录成功后颁发一个证书(加密的用户凭证),用来标识用户的身份。比如cookie认证方案,添加一个cookie;jwtbearer认证方案不实现此方法
- SignOutAsync:退出登录,比如cookie认证方案,清除Coookie;jwtbearer认证方案不实现此方法
以上方法都是通过从DI系统中获取到 IAuthenticationService
接口实例,然后调用其同名方法
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;
}
如上,AddAuthenticationCore
中注册了认证系统的三大核心对象:IAuthenticationSchemeProvider
,IAuthenticationHandlerProvider
和 IAuthenticationService
,以及一个对Claim进行转换的 IClaimsTransformation
(不常用),下面就来介绍一下这三大对象。
AuthenticationSchemeProvider
它是身份验证方案(schema)的容器(Dictionary<方案名,身份验证方案>
),默认是单例形式注册到依赖注入容器的
在应用启动时通过AuthenticationOptions添加的各种身份验证方案会被存储到这个容器中
IAuthenticationSchemeProvider接口
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();
}
各种GetDefaultXXX用来获取针对特定步骤的默认方案,如:GetDefaultAuthenticateSchemeAsync
.中间件从请求获取用户标识时用来获取针对此步骤的默认方案,GetDefaultSignInSchemeAsync
获取默认用来登录的方案...等等,身份验证的不同步骤可以设置不同的默认方案。如果针对单独的步骤没有设置默认方案,则自动尝试获取默认方案,通过AuthenticationOptions
设置这些默认值
身份验证过程中各个步骤都会通过AuthenticationSchemeProvider
对象拿到指定方案,并通过关联的身份验证类型获得最终身份验证处理器,然后做相应处理
由于AuthenticationSchemeProvider
的构造函数定义了IOptions<AuthenticationOptions>
参数,上面说了AuthenticationOptions
就包含上面注册进去的身份验证方案列表,AuthenticationSchemeProvider
在构造函数中遍历,将所有注册的身份验证方案存储到自身的IDictionary<string, AuthenticationScheme> _schemes
变量中
public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
{
public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
: this(options, new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal))
{
}
protected AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes)
{
_options = options.Value;
_schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
_requestHandlers = new List<AuthenticationScheme>();
foreach (var builder in _options.Schemes)
{
var scheme = builder.Build();
//options中的scheme添加到_schemes
AddScheme(scheme);
}
}
private readonly AuthenticationOptions _options;
private readonly object _lock = new object();
//已注册的scheme
private readonly IDictionary<string, AuthenticationScheme> _schemes;
//实现了IAuthenticationRequestHandler接口的Scheme
private readonly List<AuthenticationScheme> _requestHandlers;
//用作枚举api的安全返回值(防止遍历的时候删除元素抛出异常)
private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();
private Task<AuthenticationScheme?> GetDefaultSchemeAsync()
=> _options.DefaultScheme != null
? GetSchemeAsync(_options.DefaultScheme)
: Task.FromResult<AuthenticationScheme?>(null);
public virtual Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync()
=> _options.DefaultAuthenticateScheme != null
? GetSchemeAsync(_options.DefaultAuthenticateScheme)
: GetDefaultSchemeAsync();
public virtual Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync()
=> _options.DefaultChallengeScheme != null
? GetSchemeAsync(_options.DefaultChallengeScheme)
: GetDefaultSchemeAsync();
public virtual Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync()
=> _options.DefaultForbidScheme != null
? GetSchemeAsync(_options.DefaultForbidScheme)
: GetDefaultChallengeSchemeAsync();
public virtual Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync()
=> _options.DefaultSignInScheme != null
? GetSchemeAsync(_options.DefaultSignInScheme)
: GetDefaultSchemeAsync();
public virtual Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync()
=> _options.DefaultSignOutScheme != null
? GetSchemeAsync(_options.DefaultSignOutScheme)
: GetDefaultSignInSchemeAsync();
public virtual Task<AuthenticationScheme?> GetSchemeAsync(string name)
=> Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);
public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
=> Task.FromResult(_requestHandlersCopy);
public virtual bool TryAddScheme(AuthenticationScheme scheme)
{
if (_schemes.ContainsKey(scheme.Name))
{
return false;
}
lock (_lock)
{
if (_schemes.ContainsKey(scheme.Name))
{
return false;
}
if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
{
_requestHandlers.Add(scheme);
_requestHandlersCopy = _requestHandlers.ToArray();
}
_schemes[scheme.Name] = scheme;
_schemesCopy = _schemes.Values.ToArray();
return true;
}
}
public virtual void AddScheme(AuthenticationScheme scheme)
{
if (_schemes.ContainsKey(scheme.Name))
{
throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
}
lock (_lock)
{
if (!TryAddScheme(scheme))
{
throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
}
}
}
public virtual void RemoveScheme(string name)
{
if (!_schemes.ContainsKey(name))
{
return;
}
lock (_lock)
{
if (_schemes.ContainsKey(name))
{
var scheme = _schemes[name];
if (_requestHandlers.Remove(scheme))
{
_requestHandlersCopy = _requestHandlers.ToArray();
}
_schemes.Remove(name);
_schemesCopy = _schemes.Values.ToArray();
}
}
}
public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
=> Task.FromResult(_schemesCopy);
}
AuthenticationScheme(身份验证方案)
总结性的说:身份验证方案 = 名称 + 身份验证处理器类型 + 选项,暂时可以理解一种身份验证方式 对应 一个身份验证方案,比如:
- 基于用户名密码+cookie的身份验证方式 对应的 身份验证方案为:
new AuthenticationScheme("UIDPWDCookie",typeof(CookieAuthenticationHandler))
- 基于用户名密码+token的身份验证方式 对应的 身份验证方案为:
new AuthenticationScheme("JwtBearer",typeof(JwtBearerHandler))
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; }
}
身份验证方案在程序启动阶段配置,启动后形成一个身份验证方案列表。
程序运行阶段从这个列表中取出指定方案,得到对应的处理器类型,然后创建它,最后调用这个处理器做相应处理
比如登录操作的Action中HttpContext.SignIn("方案名") > 通过方案名找到方案从而得到对应的处理器类型 > 创建处理器 > 调用其SignIn方法
一种特殊的情况可能多种方案使用同一个身份验证处理器类型,这个后续的集成第三方登录来说
AuthenticationHandlerProvider(身份验证处理器工厂)
可以把它理解为AuthenticationHandler
的运行时容器或工厂
它是以Scope的形式注册到依赖注入容器的,所以每次请求都会创建一个实例对象。
唯一方法GetHandlerAsync
从AuthenticationSchemeProvider
获取指定身份验证方案,然后通过方案关联的AuthenticationHandler Type
从依赖注入容器中获取AuthenticationHandler
,获取的AuthenticationHandler
会被缓存,这样同一个请求的后续调用直接从缓存中拿。
AuthenticationService
就是通过它来得到AuthenticationHandler
然后完成身份验证各种功能的
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
}
public IAuthenticationSchemeProvider Schemes { get; }
// handler缓存
private readonly Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
public async Task<IAuthenticationHandler?> GetHandlerAsync(HttpContext context, string authenticationScheme)
{
if (_handlerMap.TryGetValue(authenticationScheme, out var value))
{
return value;
}
var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
if (scheme == null)
{
return null;
}
//ActivatorUtilities定义在Microsoft.Extensions.DependencyInjection.Abstractions中
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;
}
}
AuthenticationHandler(身份验证处理器)
AuthenticationHandler
中包含身份验证流程中核心的处理步骤,下面以cookie身份验证为例:
- SignIn:在登录时验证通过后将用户标识加密后存储到cookie
- SignOut:当用户注销时,需要清除代表用户标识的cookie
- Authenticate:在登录时从请求中获取用户标识
- Challenge:质询/挑战,意思是当发现没有从当前请求中发现用户标识时怎么办,可能是跳转到登录页,也可能是直接响应401,或者跳转到第三方(如QQ、微信)的登录页
- Forbid:如权限验证不通过
身份验证处理器就是用来跟身份验证相关的步骤的,这些步骤在系统的不同地方来调用(比如在登录页对应的Action、在请求抵达时、在授权中间件中),
每个调用时都可以指定使用哪种身份验证方案,如果不提供将使用默认方案来做对应的操作。
不同的身份验证方式有不同的AuthenticationHandler实现
IAuthenticationHandler
IAuthenticationHandler接口只定义了最核心的几个步骤:Authenticate()、Challenge()、Forbid()。登录和注销这两个步骤定义了对应的子接口。
身份验证服务AuthenticationService
身份验证中的步骤是在多个地方被调用的,身份验证中间件、授权中间件、登录的Action(如:AccountController.SignIn())、注销的Action(如:AccountController.SignOut()),身份验证的核心方法定义在这个类中,但它本质上还是去找到对应的身份验证处理器并调用其同名方法。其实这些方法还进一步以扩展方法的形式定义到HttpContext
上了。以SignIn
方法为例
HttpContext.SignIn() > AuthenticationService.SignIn() > AuthenticationHandler.SignIn()
IAuthenticationService
IAuthenticationService
是用来对外提供一个统一的认证服务接口
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);
}
IAuthenticationService 的默认实现AuthenticationService
中的逻辑就非常简单了,只是调用Handler中的同名方法:
public class AuthenticationService : IAuthenticationService
{
public IAuthenticationSchemeProvider Schemes { get; }
public IAuthenticationHandlerProvider Handlers { get; }
public IClaimsTransformation Transform { get; }
public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
if (scheme == null)
{
var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
scheme = defaultScheme?.Name;
if (scheme == null)
{
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
}
}
var handler = await Handlers.GetHandlerAsync(context, scheme);
var result = await handler.AuthenticateAsync();
if (result != null && result.Succeeded)
{
var transformed = await Transform.TransformAsync(result.Principal);
return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
}
return result;
}
}