一般在应用程序中,安全分为先后两个步骤:验证和受权。验证负责检查当前请求者的身份,而受权则根据上一步获得的身份决定当前请求者是否可以访问指望的资源。git
既然安全从验证开始,咱们也就从验证开始介绍安全。github
咱们先从比较简单的场景开始考虑,例如在 Web API 开发中,须要验证请求方是否提供了安全令牌,安全令牌是否有效。若是无效,那么 API 端应该拒绝提供服务。在命名空间 Microsoft.AspNetCore.Authentication 下,定义关于验证的核心接口。对应的程序集是 Microsoft.AspNetCore.Authentication.Abstractions.dll。安全
在 ASP.NET 下,验证中包含 3 个基本操做:架构
验证操做负责基于当前请求的上下文,使用来自请求中的信息,例如请求头、Cookie 等等来构造用户标识。构建的结果是一个 AuthenticateResult 对象,它指示了验证是否成功,若是成功的话,用户标识将能够在验证票据中找到。async
常见的验证包括:ide
在受权管理阶段,若是用户没有获得验证,但所指望访问的资源要求必须获得验证的时候,受权服务会发出质询。例如,当匿名用户访问受限资源的时候,或者当用户点击登陆连接的时候。受权服务会经过质询来相应用户。函数
例如网站
质询操做应该让用户知道应该使用何种验证机制来访问请求的资源。ui
在受权管理阶段,若是用户已经经过了验证,可是对于其访问的资源并无获得许可,此时会使用拒绝操做。this
例如:
拒绝访问处理应该让用户知道:
在这个场景下,能够看到,验证须要提供的基本功能就包括了验证和验证失败后的拒绝服务两个操做。在 ASP.NET Core 中,验证被称为 Authenticate,拒绝被称为 Forbid。 在供消费者访问的网站上,若是咱们但愿在验证失败后,不是像 API 同样直接返回一个错误页面,而是将用户导航到登陆页面,那么,就还须要增长一个操做,这个操做的本质是但愿用户再次提供安全凭据,在 ASP.NET Core 中,这个操做被称为 Challenge。这 3 个操做结合在一块儿,就是验证最基本的要求,以接口形式表示,就是 IAuthenticationHandler 接口,以下所示:
public interface IAuthenticationHandler { Task InitializeAsync(AuthenticationScheme scheme, HttpContext context); Task<AuthenticateResult> AuthenticateAsync(); Task ChallengeAsync(AuthenticationProperties? properties); Task ForbidAsync(AuthenticationProperties? properties); }
验证的结果是一个 AuthenticateResult 对象。值得注意的是,它还提供了一个静态方法 NoResult() 用来返回没有获得结果,静态方法 Fail() 生成一个表示验证异常的结果,而 Success() 成功则须要提供验证票据。
经过验证以后,会返回一个包含了请求者票据的验证结果。
namespace Microsoft.AspNetCore.Authentication { public class AuthenticateResult { // ...... public static AuthenticateResult NoResult() { return new AuthenticateResult() { None = true }; } public static AuthenticateResult Fail(Exception failure) { return new AuthenticateResult() { Failure = failure }; } public static AuthenticateResult Success(AuthenticationTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties }; } public static AuthenticateResult Success(AuthenticationTicket ticket) { if (ticket == null) { throw new ArgumentNullException(nameof(ticket)); } return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties }; } // ...... } }
在 GitHub 中查看 AuthenticateResult 源码
那么验证的信息来自哪里呢?除了前面介绍的 3 个操做以外,还要求一个初始化的操做 Initialize,经过这个方法来提供当前请求的上下文信息。
在 GitHub 中查看 IAuthenticationHandler 定义
有的时候,咱们还但愿提供登出操做,增长登出操做的接口被称为 IAuthenticationSignOutHandler。
public interface IAuthenticationSignOutHandler : IAuthenticationHandler { Task SignOutAsync(AuthenticationProperties? properties); }
在 GitHub 中查看 IAuthenticationSignOutHandler 源码
在登出的基础上,若是还但愿提供登陆操做,那么就是 IAuthenticationSignInHandler 接口。
public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler { Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties); }
在 GitHub 中查看 IAuthenticationSignInHandler 源码
直接实现接口仍是比较麻烦的,在命名空间 Microsoft.AspNetCore.Authentication 下,微软提供了抽象基类 AuthenticationHandler 以方便验证控制器的开发,其它控制器能够从该控制器派生,以取得其提供的服务。
namespace Microsoft.AspNetCore.Authentication { public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new() { protected AuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) { Logger = logger.CreateLogger(this.GetType().FullName); UrlEncoder = encoder; Clock = clock; OptionsMonitor = options; } } // ...... }
经过类的定义能够看到,它使用了泛型。每一个控制器应该有一个对应该控制器的配置选项,经过泛型来指定验证处理器所使用的配置类型,在构造函数中,能够看到它被用于获取对应的配置选项对象。
在 GitHub 中查看 AuthenticationHandler 源码
经过 InitializeAsync(),验证处理器能够得到当前请求的上下文对象 HttpContext。
public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
最终,做为抽象类的 ,但愿派生类来完成这个验证任务,抽象方法 HandleAuthenticateAsync() 提供了扩展点。
/// <summary> /// Allows derived types to handle authentication. /// </summary> /// <returns>The <see cref="AuthenticateResult"/>.</returns> protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
验证的结果是一个 AuthenticateResult。
而拒绝服务则简单的多,直接在这个抽象基类中提供了默认实现。直接返回 HTTP 403。
protected virtual Task HandleForbiddenAsync(AuthenticationProperties properties) { Response.StatusCode = 403; return Task.CompletedTask; }
剩下的一个也同样,提供了默认实现。直接返回 HTTP 401 响应。
protected virtual Task HandleChallengeAsync(AuthenticationProperties properties) { Response.StatusCode = 401; return Task.CompletedTask; }
对于 JWT 来讲,并不涉及到登入和登出,因此它须要从实现 IAuthenticationHandler 接口的抽象基类 AuthenticationHandler 派生出来便可。从 AuthenticationHandler 派生出来的 JwtBearerHandler 实现基于本身的配置选项 JwtBearerOptions。因此该类定义就变得以下所示,而构造函数显然配合了抽象基类的要求。
namespace Microsoft.AspNetCore.Authentication.JwtBearer { public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions> { public JwtBearerHandler( IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } // ...... } }
在 GitHub 中查看 JwtBearerHandler 源码
真正的验证则在 HandleAuthenticateAsync() 中实现。下面的代码是否是就很熟悉了,从请求头中获取附带的 JWT 访问令牌,而后验证该令牌的有效性,核心代码以下所示。
string authorization = Request.Headers[HeaderNames.Authorization]; // If no authorization header found, nothing to process further if (string.IsNullOrEmpty(authorization)) { return AuthenticateResult.NoResult(); } if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { token = authorization.Substring("Bearer ".Length).Trim(); } // If no token found, no further work possible if (string.IsNullOrEmpty(token)) { return AuthenticateResult.NoResult(); } // ...... principal = validator.ValidateToken(token, validationParameters, out validatedToken);
在 GitHub 中查看 JwtBearerHandler 源码
在 ASP.NET Core 中,你可使用各类验证处理器,并不只仅只能使用一个,验证控制器须要一个名称,它被看做该验证模式 Schema 的名称。Jwt 验证模式的默认名称就是 "Bearer",经过字符串常量 JwtBearerDefaults.AuthenticationScheme 定义。
namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary> /// Default values used by bearer authentication. /// </summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } }
在 GitHub 中查看 JwtBearerDefaults 源码
最终经过 AuthenticationBuilder 的扩展方法 AddJwtBearer() 将 Jwt 验证控制器注册到依赖注入的容器中。
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder) => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { }); public static AuthenticationBuilder AddJwtBearer( this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions) { builder.Services.TryAddEnumerable( ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>()); return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>( authenticationScheme, displayName, configureOptions); }
在 GitHub 中查看 JwtBearerExtensions 扩展方法源码
一种验证处理器,加上对应的验证配置选项,咱们再为它起一个名字,组合起来就成为一种验证架构 Schema。在 ASP.NET Core 中,能够注册多种验证架构。例如,受权策略可使用架构的名称来指定所使用的验证架构来使用特定的验证方式。在配置验证的时候,一般设置默认的验证架构。当没有指定验证架构的时候,就会使用默认架构进行处理。
还能够
注册的验证模式,最终变成 AuthenticationScheme,注册到依赖注入服务中。
public class AuthenticationScheme { public string Name { get; } public string? DisplayName { get; } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] public Type HandlerType { get; } }
在 GitHub 中查看 AuthenticationScheme 源码
各类验证架构被保存到一个 IAuthenticationSchemeProvider 中。
public interface IAuthenticationSchemeProvider { Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync(); Task<AuthenticationScheme?> GetSchemeAsync(string name); void AddScheme(AuthenticationScheme scheme); void RemoveScheme(string name); }
在 GitHub 中查看 IAuthenticationSchemeProvider 源码
最终的使用是经过 IAuthenticationHandlerProvider 来实现的,经过一个验证模式的字符串名称,能够取得所对应的验证控制器。
public interface IAuthenticationHandlerProvider { Task<IAuthenticationHandler?> GetHandlerAsync(HttpContext context, string authenticationScheme); }
在 GitHub 中查看 IAuthenticationHandlerProvider 源码
它的默认实现是 AuthenticationHandlerProvider,源码并不复杂。
public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider { public IAuthenticationSchemeProvider Schemes { get; } private readonly Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal); public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes) { Schemes = schemes; } 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; } 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; } }
在 GitHub 中查看 AuthenticationHandlerProvider 源码
验证中间件的处理就没有那么复杂了。
找到默认的验证模式,使用默认验证模式的名称取得对应的验证处理器,若是验证成功的话,把当前请求用户的主体放到当前请求上下文的 User 上。
里面还有一段特别的代码,用来找出哪些验证处理器实现了 IAuthenticationHandlerProvider,并依次调用它们,看看是否须要提取终止请求处理过程。
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Authentication { public class AuthenticationMiddleware { private readonly RequestDelegate _next; public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (schemes == null) { throw new ArgumentNullException(nameof(schemes)); } _next = next; Schemes = schemes; } public IAuthenticationSchemeProvider Schemes { get; set; } public async Task Invoke(HttpContext context) { context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase }); // Give any IAuthenticationRequestHandler schemes a chance to handle the request var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { return; } } var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await context.AuthenticateAsync(defaultAuthenticate.Name); if (result?.Principal != null) { context.User = result.Principal; } } await _next(context); } } }
在 GitHub 中查看 AuthenticationMiddle 源码