【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 中注册了认证系统的三大核心对象:IAuthenticationSchemeProviderIAuthenticationHandlerProvider 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的形式注册到依赖注入容器的,所以每次请求都会创建一个实例对象。
唯一方法GetHandlerAsyncAuthenticationSchemeProvider获取指定身份验证方案,然后通过方案关联的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;
    }
}
posted @ 2019-11-23 17:22  .Neterr  阅读(395)  评论(0编辑  收藏  举报