• [转]ASP.NET Core 运行原理解剖:Authentication


           在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等。在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现代化的需求,因此在ASP.NET Core 中对认证及授权进行了全新设计,使其更加灵活,可以应付各种场景。在上一章中,我们提到HttpContext中认证相关的功能放在了独立的模块中,以扩展的方式来展现,以保证HttpContext的简洁性,本章就来介绍一下 ASP.NET Core 认证系统的整个轮廓,以及它的切入点。

    AuthenticationHttpContextExtensions

    AuthenticationHttpContextExtensions 类是对 HttpContext 认证相关的扩展,它提供了如下扩展方法:

        //
        // 摘要:
        //     Extension methods to expose Authentication on HttpContext.
        public static class AuthenticationHttpContextExtensions
        {
            //
            // 摘要:
            //     Extension method for authenticate using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultAuthenticateScheme
            //     scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            // 返回结果:
            //     The Microsoft.AspNetCore.Authentication.AuthenticateResult.
            public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context);
            //
            // 摘要:
            //     Extension method for authenticate.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The Microsoft.AspNetCore.Authentication.AuthenticateResult.
            public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme);
            //
            // 摘要:
            //     Extension method for Challenge.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The result.
            public static Task ChallengeAsync(this HttpContext context, string scheme);
            //
            // 摘要:
            //     Extension method for authenticate using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultChallengeScheme
            //     scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            // 返回结果:
            //     The task.
            public static Task ChallengeAsync(this HttpContext context);
            //
            // 摘要:
            //     Extension method for authenticate using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultChallengeScheme
            //     scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for Challenge.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for Forbid.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for Forbid.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for Forbid.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The task.
            public static Task ForbidAsync(this HttpContext context, string scheme);
            //
            // 摘要:
            //     Extension method for Forbid using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultForbidScheme
            //     scheme..
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            // 返回结果:
            //     The task.
            public static Task ForbidAsync(this HttpContext context);
            //
            // 摘要:
            //     Extension method for getting the value of an authentication token.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   tokenName:
            //     The name of the token.
            //
            // 返回结果:
            //     The value of the token.
            public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName);
            //
            // 摘要:
            //     Extension method for getting the value of an authentication token.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   tokenName:
            //     The name of the token.
            //
            // 返回结果:
            //     The value of the token.
            public static Task<string> GetTokenAsync(this HttpContext context, string tokenName);
            //
            // 摘要:
            //     Extension method for SignIn.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   principal:
            //     The user.
            //
            // 返回结果:
            //     The task.
            public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal);
            //
            // 摘要:
            //     Extension method for SignIn using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignInScheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   principal:
            //     The user.
            //
            // 返回结果:
            //     The task.
            public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal);
            //
            // 摘要:
            //     Extension method for SignIn using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignInScheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   principal:
            //     The user.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for SignIn.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   principal:
            //     The user.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for SignOut using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignOutScheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            // 返回结果:
            //     The task.
            public static Task SignOutAsync(this HttpContext context);
            //
            // 摘要:
            //     Extension method for SignOut using the Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignOutScheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            //
            // 返回结果:
            //     The task.
            public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties);
            //
            // 摘要:
            //     Extension method for SignOut.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The task.
            public static Task SignOutAsync(this HttpContext context, string scheme);
            //
            // 摘要:
            //     Extension method for SignOut.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties properties.
            public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties);
        }

    主要包括如下6个扩展方法,其它的只是一些参数重载:

    • SignInAsync用户登录成功后颁发一个证书(加密的用户凭证),用来标识用户的身份。

    • SignOutAsync退出登录,如清除Coookie等。

    • AuthenticateAsync验证在 SignInAsync 中颁发的证书,并返回一个 AuthenticateResult 对象,表示用户的身份。

    • ChallengeAsync返回一个需要认证的标识来提示用户登录,通常会返回一个 401 状态码。

    • ForbidAsync禁上访问,表示用户权限不足,通常会返回一个 403 状态码。

    • GetTokenAsync用来获取 AuthenticationProperties 中保存的额外信息。

    它们的实现都非常简单,与展示的第一个方法类似,从DI系统中获取到 IAuthenticationService 接口实例,然后调用其同名方法。

    因此,如果我们希望使用认证服务,那么首先要注册 IAuthenticationService 的实例,ASP.NET Core 中也提供了对应注册扩展方法:

        //
        // 摘要:
        //     Used to provide authentication.
        public interface IAuthenticationService
        {
            //
            // 摘要:
            //     Authenticate for the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The result.
            Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
            //
            // 摘要:
            //     Challenge the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Forbids the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Sign a principal in for the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   principal:
            //     The System.Security.Claims.ClaimsPrincipal to sign in.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
            //
            // 摘要:
            //     Sign out the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
        }

    如上,AddAuthenticationCore中注册了认证系统的三大核心对象:IAuthenticationSchemeProvider,IAuthenticationHandlerProvider 和 IAuthenticationService,以及一个对Claim进行转换的 IClaimsTransformation(不常用),下面就来介绍一下这三大对象。

    IAuthenticationSchemeProvider

    首先来解释一下 Scheme是用来做什么的。因为在 ASP.NET Core 中可以支持各种各样的认证方式(如,cookie, bearer, oauth, openid 等等),而 Scheme 用来标识使用的是哪种认证方式,不同的认证方式其处理方式是完全不一样的,所以Scheme是非常重要的。

    IAuthenticationSchemeProvider 用来提供对Scheme的注册和查询,定义如下:

        //
        // 摘要:
        //     Responsible for managing what authenticationSchemes are supported.
        public interface IAuthenticationSchemeProvider
        {
            //
            // 摘要:
            //     Registers a scheme for use by Microsoft.AspNetCore.Authentication.IAuthenticationService.
            //
            // 参数:
            //   scheme:
            //     The scheme.
            void AddScheme(AuthenticationScheme scheme);
            //
            // 摘要:
            //     Returns all currently registered Microsoft.AspNetCore.Authentication.AuthenticationSchemes.
            //
            // 返回结果:
            //     All currently registered Microsoft.AspNetCore.Authentication.AuthenticationSchemes.
            Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
            //
            // 摘要:
            //     Returns the scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.AuthenticateAsync(Microsoft.AspNetCore.Http.HttpContext,System.String).
            //     This is typically specified via Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultAuthenticateScheme.
            //     Otherwise, this will fallback to Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultScheme.
            //
            // 返回结果:
            //     The scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.AuthenticateAsync(Microsoft.AspNetCore.Http.HttpContext,System.String).
            Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();
            //
            // 摘要:
            //     Returns the scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.ChallengeAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            //     This is typically specified via Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultChallengeScheme.
            //     Otherwise, this will fallback to Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultScheme.
            //
            // 返回结果:
            //     The scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.ChallengeAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync();
            //
            // 摘要:
            //     Returns the scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.ForbidAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            //     This is typically specified via Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultForbidScheme.
            //     Otherwise, this will fallback to Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider.GetDefaultChallengeSchemeAsync
            //     .
            //
            // 返回结果:
            //     The scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.ForbidAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            Task<AuthenticationScheme> GetDefaultForbidSchemeAsync();
            //
            // 摘要:
            //     Returns the scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.SignInAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,System.Security.Claims.ClaimsPrincipal,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            //     This is typically specified via Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignInScheme.
            //     Otherwise, this will fallback to Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultScheme.
            //
            // 返回结果:
            //     The scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.SignInAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,System.Security.Claims.ClaimsPrincipal,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();
            //
            // 摘要:
            //     Returns the scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.SignOutAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            //     This is typically specified via Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultSignOutScheme.
            //     Otherwise, this will fallback to Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider.GetDefaultSignInSchemeAsync
            //     .
            //
            // 返回结果:
            //     The scheme that will be used by default for Microsoft.AspNetCore.Authentication.IAuthenticationService.SignOutAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();
            //
            // 摘要:
            //     Returns the schemes in priority order for request handling.
            //
            // 返回结果:
            //     The schemes in priority order for request handling
            Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
            //
            // 摘要:
            //     Returns the Microsoft.AspNetCore.Authentication.AuthenticationScheme matching
            //     the name, or null.
            //
            // 参数:
            //   name:
            //     The name of the authenticationScheme.
            //
            // 返回结果:
            //     The scheme or null if not found.
            Task<AuthenticationScheme> GetSchemeAsync(string name);
            //
            // 摘要:
            //     Removes a scheme, preventing it from being used by Microsoft.AspNetCore.Authentication.IAuthenticationService.
            //
            // 参数:
            //   name:
            //     The name of the authenticationScheme being removed.
            void RemoveScheme(string name);
        }

     其 AddScheme 方法,用来注册Scheme,而每一种Scheme最终体现为一个 AuthenticationScheme 类型的对象:

        //
        // 摘要:
        //     AuthenticationSchemes assign a name to a specific Microsoft.AspNetCore.Authentication.IAuthenticationHandler
        //     handlerType.
        public class AuthenticationScheme
        {
            //
            // 摘要:
            //     Constructor.
            //
            // 参数:
            //   name:
            //     The name for the authentication scheme.
            //
            //   displayName:
            //     The display name for the authentication scheme.
            //
            //   handlerType:
            //     The Microsoft.AspNetCore.Authentication.IAuthenticationHandler type that handles
            //     this scheme.
            public AuthenticationScheme(string name, string displayName, Type handlerType);
    
            //
            // 摘要:
            //     The name of the authentication scheme.
            public string Name { get; }
            //
            // 摘要:
            //     The display name for the scheme. Null is valid and used for non user facing schemes.
            public string DisplayName { get; }
            //
            // 摘要:
            //     The Microsoft.AspNetCore.Authentication.IAuthenticationHandler type that handles
            //     this scheme.
            public Type HandlerType { get; }
        }

    每一个Scheme中还包含一个对应的IAuthenticationHandler类型的Handler,由它来完成具体的处理逻辑,看一下它的默认实现

        public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
        {
    
            private IDictionary<string, AuthenticationScheme> _map = newDictionary<string, AuthenticationScheme>(StringComparer.Ordinal);
    
            public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
            {
                _options = options.Value;
    
                foreach (varbuilder in_options.Schemes)
                {
    
                    varscheme = builder.Build(); AddScheme(scheme);
                }
            }
    
            privateTask<AuthenticationScheme> GetDefaultSchemeAsync() => _options.DefaultScheme != null ? GetSchemeAsync(_options.DefaultScheme) : Task.FromResult<AuthenticationScheme>(null); ....}
        }

    如上,通过一个内部的字典来保存我们所注册的Scheme,key为Scheme名称,然后提供一系列对该字典的查询。它还提供了一系列的GetDefaultXXXSchemeAsync方法,所使用的Key是通过构造函数中接收的AuthenticationOptions对象来获取的,如果未配置,则返回为null。

    对于 AuthenticationOptions 对象,大家可能会比较熟悉,在上面介绍的 AddAuthenticationCore 扩展方法中,也是使用该对象来配置认证系统:

        public class AuthenticationOptions
        {
            public AuthenticationOptions();
    
            //
            // 摘要:
            //     Returns the schemes in the order they were added (important for request handling
            //     priority)
            public IEnumerable<AuthenticationSchemeBuilder> Schemes { get; }
            //
            // 摘要:
            //     Maps schemes by name.
            public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; }
            //
            // 摘要:
            //     Used as the fallback default scheme for all the other defaults.
            public string DefaultScheme { get; set; }
            //
            // 摘要:
            //     Used as the default scheme by Microsoft.AspNetCore.Authentication.IAuthenticationService.AuthenticateAsync(Microsoft.AspNetCore.Http.HttpContext,System.String).
            public string DefaultAuthenticateScheme { get; set; }
            //
            // 摘要:
            //     Used as the default scheme by Microsoft.AspNetCore.Authentication.IAuthenticationService.SignInAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,System.Security.Claims.ClaimsPrincipal,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            public string DefaultSignInScheme { get; set; }
            //
            // 摘要:
            //     Used as the default scheme by Microsoft.AspNetCore.Authentication.IAuthenticationService.SignOutAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            public string DefaultSignOutScheme { get; set; }
            //
            // 摘要:
            //     Used as the default scheme by Microsoft.AspNetCore.Authentication.IAuthenticationService.ChallengeAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            public string DefaultChallengeScheme { get; set; }
            //
            // 摘要:
            //     Used as the default scheme by Microsoft.AspNetCore.Authentication.IAuthenticationService.ForbidAsync(Microsoft.AspNetCore.Http.HttpContext,System.String,Microsoft.AspNetCore.Authentication.AuthenticationProperties).
            public string DefaultForbidScheme { get; set; }
    
            //
            // 摘要:
            //     Adds an Microsoft.AspNetCore.Authentication.AuthenticationScheme.
            //
            // 参数:
            //   name:
            //     The name of the scheme being added.
            //
            //   configureBuilder:
            //     Configures the scheme.
            public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder);
            //
            // 摘要:
            //     Adds an Microsoft.AspNetCore.Authentication.AuthenticationScheme.
            //
            // 参数:
            //   name:
            //     The name of the scheme being added.
            //
            //   displayName:
            //     The display name for the scheme.
            //
            // 类型参数:
            //   THandler:
            //     The Microsoft.AspNetCore.Authentication.IAuthenticationHandler responsible for
            //     the scheme.
            public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler;
        }

    该对象可以帮助我们更加方便的注册Scheme,提供泛型和 AuthenticationSchemeBuilder 两种方式配置方式。

    到此,我们了解到,要想使用认证系统,必要先注册Scheme,而每一个Scheme必须指定一个Handler,否则会抛出异常,下面我们就来了解一下Handler。

    IAuthenticationHandlerProvider

     在 ASP.NET Core 的认证系统中,AuthenticationHandler 负责对用户凭证的验证,它定义了如下接口:

        //
        // 摘要:
        //     Created per request to handle authentication for to a particular scheme.
        public interface IAuthenticationHandler
        {
            //
            // 摘要:
            //     Authentication behavior.
            //
            // 返回结果:
            //     The Microsoft.AspNetCore.Authentication.AuthenticateResult result.
            Task<AuthenticateResult> AuthenticateAsync();
            //
            // 摘要:
            //     Challenge behavior.
            //
            // 参数:
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties that contains
            //     the extra meta-data arriving with the authentication.
            //
            // 返回结果:
            //     A task.
            Task ChallengeAsync(AuthenticationProperties properties);
            //
            // 摘要:
            //     Forbid behavior.
            //
            // 参数:
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties that contains
            //     the extra meta-data arriving with the authentication.
            //
            // 返回结果:
            //     A task.
            Task ForbidAsync(AuthenticationProperties properties);
            //
            // 摘要:
            //     The handler should initialize anything it needs from the request and scheme here.
            //
            // 参数:
            //   scheme:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme.
            //
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext context.
            Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
        }

    AuthenticationHandler的创建是通过 IAuthenticationHandlerProvider 来完成的:

    public interface IAuthenticationHandlerProvider
    {
       Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context,string authenticationScheme);
    }

    Provider 只定义了一个 GetHandlerAsync 方法,来获取指定的Scheme的Handler,在 ASP.NET Core 中,很多地方都使用了类似的 Provider模式。

    而HandlerProvider的实现,我们通过对上面SchemeProvider的了解,应该可以猜到一二,因为在 AuthenticationScheme 中已经包含了Hander:

        public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
        {
    
            public Authentication HandlerProvider(IAuthenticationSchemeProvider schemes) { Schemes = schemes; }
    
            public IAuthenticationSchemeProvider Schemes { get; }
    
            private Dictionary<string, IAuthenticationHandler> _handlerMap = newDictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
    
            public asyncTask<IAuthenticationHandler> GetHandlerAsync(HttpContext context, stringauthenticationScheme)
            {
    
                if (_handlerMap.ContainsKey(authenticationScheme))
                {
    
                    return_handlerMap[authenticationScheme];
                }
    
                varscheme = awaitSchemes.GetSchemeAsync(authenticationScheme); if (scheme == null)
                {
    
                    returnnull;
                }
                varhandler = (context.RequestServices.GetService(scheme.HandlerType) ?? ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType)) asIAuthenticationHandler; if (handler != null) { awaithandler.InitializeAsync(scheme, context); _handlerMap[authenticationScheme] = handler; }
                return handler;
            }
        }

    可以看到,AuthenticationHandlerProvider 首先使用 IAuthenticationSchemeProvider 获取到当前Scheme,然后先从DI中查找是否有此Scheme中的Handler,如果未注册到DI系统中,则使用 ActivatorUtilities 来创建其实例,并缓存到内部的 _handlerMap 字典中。

    IAuthenticationService

    IAuthenticationService 本质上是对 IAuthenticationSchemeProvider 和 IAuthenticationHandlerProvider 封装,用来对外提供一个统一的认证服务接口:

        //
        // 摘要:
        //     Used to provide authentication.
        public interface IAuthenticationService
        {
            //
            // 摘要:
            //     Authenticate for the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            // 返回结果:
            //     The result.
            Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
            //
            // 摘要:
            //     Challenge the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Forbids the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
            //
            // 摘要:
            //     Sign a principal in for the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   principal:
            //     The System.Security.Claims.ClaimsPrincipal to sign in.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
            //
            // 摘要:
            //     Sign out the specified authentication scheme.
            //
            // 参数:
            //   context:
            //     The Microsoft.AspNetCore.Http.HttpContext.
            //
            //   scheme:
            //     The name of the authentication scheme.
            //
            //   properties:
            //     The Microsoft.AspNetCore.Authentication.AuthenticationProperties.
            //
            // 返回结果:
            //     A task.
            Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
        }

    这5个方法中,都需要接收一个 scheme 参数,因为只有先指定你要使用的认证方式,才能知道该如何进行认证。

    对于上面的前三个方法,我们知道在IAuthenticationHandler中都有对应的实现,而SignInAsync和SignOutAsync则使用了独立的定义接口:

        public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
        {
    
            Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
        }
    
        public interface IAuthenticationSignOutHandler : IAuthenticationHandler { Task SignOutAsync(AuthenticationProperties properties); }

    SignInAsync 和 SignOutAsync 之所以使用独立的接口,是因为在现代架构中,通常会提供一个统一的认证中心,负责证书的颁发及销毁(登入和登出),而其它服务只用来验证证书,并用不到SingIn/SingOut。

    而 IAuthenticationService 的默认实现 AuthenticationService 中的逻辑就非常简单了,只是调用Handler中的同名方法:

        public class AuthenticationService : IAuthenticationService
        {
    
            public IAuthenticationSchemeProvider Schemes { get; }
    
            public IAuthenticationHandlerProvider Handlers { get; }
    
            public IClaimsTransformation Transform { get; }
    
            public virtual asyncTask<AuthenticateResult> AuthenticateAsync(HttpContext context, stringscheme)
            {
    
                if (scheme == null)
                {
    
                    vardefaultScheme = awaitSchemes.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));
                }
    
                returnresult;
            }
        }

    AuthenticationService中对这5个方法的实现大致相同,首先会在我们传入的scheme为null时,来获取我们所注册的默认scheme,然后获取调用相应Handler的即可。

    针对 SignInAsync 和 SignOutAsync 的实现则会判断Handler是否实现了对应的接口,若未实现则抛出异常。

    不过在这里还涉及到如下两个对象:

    AuthenticateResult

     AuthenticateResult 用来表示认证的结果:

        public class AuthenticateResult
        {
    
            public AuthenticationTicket Ticket { get; protected set; }
    
            public bool Succeeded => Ticket != null;
    
            public ClaimsPrincipal Principal => Ticket?.Principal;
    
            public AuthenticationProperties Properties => Ticket?.Properties;
    
            public Exception Failure { get; protected set; }
    
            public bool None { get; protected set; }
    
            public static AuthenticateResult Success(AuthenticationTicket ticket) => new AuthenticateResult() { Ticket = ticket };
    
            public static AuthenticateResult NoResult() => new AuthenticateResult() { None = true };
    
            public static AuthenticateResult Fail(Exception failure) => new AuthenticateResult() { Failure = failure };
    
            public static AuthenticateResult Fail(string failureMessage) => new AuthenticateResult() { Failure = new Exception(failureMessage) };
        }

    它主要包含一个核心属性 AuthenticationTicket:

        public class AuthenticationTicket
        {
            public string AuthenticationScheme { get; private set; }
    
            public ClaimsPrincipal Principal { get; private set; }
    
            public AuthenticationProperties Properties { get; private set; }
        }

    我们可以把AuthenticationTicket看成是一个经过认证后颁发的证书,

    其 ClaimsPrincipal 属性我们较为熟悉,表示证书的主体,在基于声明的认证中,用来标识一个人的身份(如:姓名,邮箱等等),后续会详细介绍一下基于声明的认证。

    而 AuthenticationProperties 属性用来表示证书颁发的相关信息,如颁发时间,过期时间,重定向地址等等:

        public class AuthenticationProperties
        {
    
            public IDictionary<string, string> Items { get; }
    
            public string RedirectUri
            {
    
                get
                {
    
                    string value;
    
                    return Items.TryGetValue(RedirectUriKey, outvalue) ? value : null;
                }
    
                set
                {
    
                    if (value != null) Items[RedirectUriKey] = value;
    
                    else
                    {
    
                        if (Items.ContainsKey(RedirectUriKey)) Items.Remove(RedirectUriKey);
                    }
                }
            } 
            ...
        }

    在上面最开始介绍的HttpContext中的 GetTokenAsync 扩展方法便是对AuthenticationProperties的扩展:

        public static class AuthenticationTokenExtensions
        {
    
            private static string TokenNamesKey = ".TokenNames";
    
            private static string TokenKeyPrefix = ".Token.";
    
            public static void StoreTokens(this AuthenticationProperties properties, IEnumerable<AuthenticationToken> tokens) { }
    
            public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue) { }
    
            public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties) { }
    
            public static string GetTokenValue(this AuthenticationProperties properties, string tokenName)
            {
    
                var tokenKey = TokenKeyPrefix + tokenName;
    
                return properties.Items.ContainsKey(tokenKey) ? properties.Items[tokenKey] : null;
            }
    
            public static Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName) => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
    
            public static async Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string scheme, string tokenName)
    
            {
    
                var result = await auth.AuthenticateAsync(context, scheme); return result?.Properties?.GetTokenValue(tokenName);
            }
        }

    如上,Token扩展只是对AuthenticationProperties中的 Items 属性进行添加和读取。

    IClaimsTransformation

    IClaimsTransformation 用来对由我们的应用程序传入的 ClaimsPrincipal 进行转换,它只定义了一个 Transform 方法:

        public interface IClaimsTransformation
        {
    
            Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
        }

    其默认实现,不做任何处理,直接返回。它适合于全局的为 ClaimsPrincipal 添加一些预定义的声明,如添加当前时间等,然后在DI中把我们的实现注册进去即可。

    Usage

    下面我们演示一下 ASP.NET Core 认证系统的实际用法:

    首先,我们要定义一个Handler:

    
    
        public class MyHandler : IAuthenticationHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
        {
    
            public AuthenticationScheme Scheme { get; private set; }
    
            protected HttpContext Context { get; private set; }
    
            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
            {
                Scheme = scheme;
    
                Context = context;
    
                return Task.CompletedTask;
            }
    
            public async Task<AuthenticateResult> AuthenticateAsync()
            {
                var cookie = Context.Request.Cookies["mycookie"];
    
                if (string.IsNullOrEmpty(cookie))
                {
    
                    return AuthenticateResult.NoResult();
                }
    
                return AuthenticateResult.Success(Deserialize(cookie));
            }
    
            public Task ChallengeAsync(AuthenticationProperties properties)
            {
                Context.Response.Redirect("/login");
    
                return Task.CompletedTask;
            }
    
            public Task ForbidAsync(AuthenticationProperties properties)
            {
                Context.Response.StatusCode = 403;
    
                return Task.CompletedTask;
            }
    
            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
            {
                var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
    
                Context.Response.Cookies.Append("myCookie", Serialize(ticket));
    
                return Task.CompletedTask;
            }
    
            public Task SignOutAsync(AuthenticationProperties properties)
            {
                Context.Response.Cookies.Delete("myCookie");
    
                return Task.CompletedTask;
            }
        }

    如上,在 SignInAsync 中将用户的Claim序列化后保存到Cookie中,在 AuthenticateAsync 中从Cookie中读取并反序列化成用户Claim。

    然后在DI系统中注册我们的Handler和Scheme:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthenticationCore(options => options.AddScheme<MyHandler>("myScheme", "demo scheme"));
        }

    最后,便可以通过HttpContext来调用认证系统了:

        public void Configure(IApplicationBuilder app)
        {
            // 登录
            app.Map("/login", builder => builder.Use(next =>
            {
                return async (context) =>
                {
                    var claimIdentity = new ClaimsIdentity();
    
                    claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "jim"));
    
                    await context.SignInAsync("myScheme", new ClaimsPrincipal(claimIdentity));
                };
            }));
            // 退出
            app.Map("/logout", builder => builder.Use(next =>
            {
                return async (context) =>
                {
                    await context.SignOutAsync("myScheme");
                };
            }));
            // 认证
            app.Use(next =>
            {
                return async (context) =>
                {
                    var result = await context.AuthenticateAsync("myScheme");
    
                    if (result?.Principal != null) context.User = result.Principal;
    
                    await next(context);
                };
            });
            // 授权
            app.Use(async (context, next) =>
            {
                var user = context.User;
    
                if (user?.Identity?.IsAuthenticated ?? false)
                {
                    if (user.Identity.Name != "jim") await context.ForbidAsync("myScheme");
    
                    else await next();
                }
                else
                {
                    await context.ChallengeAsync("myScheme");
                }
            });
            // 访问受保护资源
            app.Map("/resource", builder => builder.Run(async (context) => await context.Response.WriteAsync("Hello, ASP.NET Core!")));
        }

    在这里完整演示了 ASP.NET Core 认证系统的基本用法,当然,在实际使用中要比这更加复杂,如安全性,易用性等方面的完善,但本质上也就这么多东西。

    总结

    本章基于 HttpAbstractions 对 ASP.NET Core 认证系统做了一个简单的介绍,但大多是一些抽象层次的定义,并未涉及到具体的实现。

    因为现实中有各种各样的场景无法预测,HttpAbstractions提供了统一的认证规范,在我们的应用程序中,可以根据具体需求来灵活的扩展适合的认证方式。

    不过在 Security 提供了更加具体的实现方式,也包含了 Cookie, JwtBearer, OAuth, OpenIdConnect 等较为常用的认证实现。

    在下个系列会来详细介绍一下 ASP.NET Core 的认证与授权,更加偏向于实战,敬请期待!

    ASP.NET Core 在GitHub上的开源地址为:https://github.com/aspnet,包含了100多个项目,ASP.NET Core 的核心是 HttpAbstractions,其它的都是围绕着 HttpAbstractions进行的扩展。

    本系列文章所涉及到的源码只包含 Hosting 和 HttpAbstractions ,它们两个已经构成了一个完整的 ASP.NET Core 运行时,不需要其它模块,就可以轻松应对一些简单的场景。

    当然,更多的时候我们还会使用比较熟悉的 Mvc 来大大提高开发速度和体验,后续再来介绍一下MVC的运行方式。

    原文地址:http://www.cnblogs.com/RainingNight/p/authentication-in-asp-net-core.html

  • 相关阅读:
    softmax和cross_entropy
    python初始化list列表(1维、2维)
    奥卡姆剃刀 (Occam Razor)
    何谓超参数?
    面试干货!21个必知数据科学面试题和答案
    计算广告算法到底要做什么?
    推荐系统的常用算法
    推荐系统常见面试题2
    推荐系统算法面试题
    mysql-面试题
  • 原文地址:https://www.cnblogs.com/OpenLJW/p/10136641.html
Copyright © 2020-2023  润新知