在上一章中,详细介绍了 ASP.NET Core 中的受权策略,在须要受权时,只须要在对应的Controler或者Action上面打上[Authorize]
特性,并指定要执行的策略名称便可,可是,受权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。html
目录git
在《(上一章》中提到,AuthorizeAttribute
只是一个简单的实现了IAuthorizeData
接口的特性,而且在 ASP.NET Core 受权系统中并无使用到它。咱们知道在认证中,还有一个UseAuthentication
扩展方法来激活认证系统,可是在受权中并无相似的机制。github
这是由于当咱们使用[Authorize]
一般是在MVC中,由MVC来负责激活受权系统。原本在这个系列的文章中,我并不想涉及到MVC的知识,可是为了能更好的理解受权系统的执行,就来简单介绍一下MVC中与受权相关的知识。mvc
当咱们使用MVC时,首先会调用MVC的AddMvc
扩展方法,用来注册一些MVC相关的服务:app
public static IMvcBuilder AddMvc(this IServiceCollection services) { var builder = services.AddMvcCore(); builder.AddAuthorization(); ... } public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder) { AddAuthorizationServices(builder.Services); return builder; } internal static void AddAuthorizationServices(IServiceCollection services) { services.AddAuthenticationCore(); services.AddAuthorization(); services.AddAuthorizationPolicyEvaluator(); services.TryAddEnumerable( ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>()); }
在上面AddAuthorizationServices
中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于AddAuthorizationPolicyEvaluator
放到后面再来介绍,咱们先来看一下MVC中的AuthorizationApplicationModelProvider
。异步
在MVC中有一个ApplicationModel
的概念,它用来封装Controller
, Filter
, ApiExplorer
等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化ApplicationModel
的各个部分,而AuthorizationApplicationModelProvider
就是用来初始化与受权相关的部分。async
public class AuthorizationApplicationModelProvider : IApplicationModelProvider { public void OnProvidersExecuting(ApplicationModelProviderContext context) { foreach (var controllerModel in context.Result.Controllers) { var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray(); if (controllerModelAuthData.Length > 0) { controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData)); } foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>()) { controllerModel.Filters.Add(new AllowAnonymousFilter()); } foreach (var actionModel in controllerModel.Actions) { var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray(); if (actionModelAuthData.Length > 0) { actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData)); } foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>()) { actionModel.Filters.Add(new AllowAnonymousFilter()); } } } } }
如上,首先查找每一个Controller中实现了IAuthorizeData
接口的特性,而后将其转化为AuthorizeFilter
并添加到Controller的Filter集合中,紧接着再查找实现了IAllowAnonymous
接口的特性,将其转化为AllowAnonymousFilter
过滤器也添加到Filter集合中,而后以一样的逻辑查找Action上的特性并添加到Action的Filter集合中。ide
其中的关键点就是将IAuthorizeData
(也就是经过咱们熟悉的[Authorize]
特性)转化为MVC中的AuthorizeFilter
过滤器:ui
public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData) { if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider)) { var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult(); return new AuthorizeFilter(policy); } else { return new AuthorizeFilter(policyProvider, authData); } }
CombineAsync
在上一章的《AuthorizationPolicy》中已经介绍过了,咱们往下看看AuthorizeFilter的实现。this
在MVC中有一个AuthorizeFilter
过滤器,相似咱们在ASP.NET 4.x中所熟悉的[Authorize]
,它实现了IAsyncAuthorizationFilter
接口,定义以下:
public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory { public AuthorizeFilter(AuthorizationPolicy policy) {} public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {} public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {} public IEnumerable<IAuthorizeData> AuthorizeData { get; } public AuthorizationPolicy Policy { get; } public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var effectivePolicy = Policy; if (effectivePolicy == null) { effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData); } var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>(); var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext); if (context.Filters.Any(item => item is IAllowAnonymousFilter)) { return; } var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context); ... // 若是受权失败,返回ChallengeResult或ForbidResult } }
AuthorizeFilter的OnAuthorizationAsync
方法会在Action执行以前触发,其调用IPolicyEvaluator
来完成受权,将执行流程切回到 ASP.NET Core 受权系统中。关于MVC中IApplicationModelProvider
以及Filter
的概念,在之后MVC系列的文章中再来详细介绍,下面就继续介绍 ASP.NET Core 的受权系统,也就是《Security》项目。
IPolicyEvaluator是MVC调用受权系统的入口点,其定义以下:
public interface IPolicyEvaluator { Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context); Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource); }
在上面介绍的AddMVC
中,调用了AddAuthorizationPolicyEvaluator
扩展方法,它有以下定义:
public static class PolicyServiceCollectionExtensions { public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services) { services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>()); return services; } }
由此可知IPolicyEvaluator
的默认实现为PolicyEvaluator
,咱们就从它入手,来一步一步解剖 ASP.NET Core 受权系统的执行步骤。
在AuthorizeFilter
中,依次调到了AuthenticateAsync
和AuthorizeAsync
方法,咱们就一一来看。
为何还有一个AuthenticateAsync
方法呢,这不是在认证阶段执行的吗?咱们看下它的实现:
public class PolicyEvaluator : IPolicyEvaluator { public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) { if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0) { ClaimsPrincipal newPrincipal = null; foreach (var scheme in policy.AuthenticationSchemes) { var result = await context.AuthenticateAsync(scheme); if (result != null && result.Succeeded) { newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal); } } if (newPrincipal != null) { context.User = newPrincipal; return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes))); } else { context.User = new ClaimsPrincipal(new ClaimsIdentity()); return AuthenticateResult.NoResult(); } } return (context.User?.Identity?.IsAuthenticated ?? false) ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult(); } }
在《上一章》中,咱们知道在AuthorizationPolicy中有AuthenticationSchemes和IAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,可是没有提到AuthenticationSchemes的调用。
那么,看到这里,也就大概明白了,它与Requirements的执行是彻底独立的,并在它以前执行,用于重置Claims,那么为何要重置呢?
在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,context.User
就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)
来赋值的,当咱们但愿使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就须要使用基于Scheme的受权来重置Claims了。
它的实现也很简单,直接使用咱们在受权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync
方法,并将生成的Claims合并,最后返回咱们熟悉的AuthenticateResult
认证结果。
接下来再看一下PolicyEvaluator的AuthorizeAsync
方法:
public class PolicyEvaluator : IPolicyEvaluator { private readonly IAuthorizationService _authorization; public PolicyEvaluator(IAuthorizationService authorization) { _authorization = authorization; } public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource) { var result = await _authorization.AuthorizeAsync(context.User, resource, policy); if (result.Succeeded) return PolicyAuthorizationResult.Success(); return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge(); } }
该方法会根据Requirements来完成受权,具体的实现是经过调用IAuthorizationService
来实现的。
最终返回的是一个PolicyAuthorizationResult
对象,并在受权失败时,根据认证结果来返回Forbid(未受权)
或Challenge(未登陆)
。
public class PolicyAuthorizationResult { private PolicyAuthorizationResult() { } public bool Challenged { get; private set; } public bool Forbidden { get; private set; } public bool Succeeded { get; private set; } }
而后就到了受权的核心对象AuthorizationService
,也能够称为受权的外交官,咱们也能够直接在应用代码中调用该对象来实现受权,它有以下定义:
public interface IAuthorizationService { Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName); Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements); }
在
AuthorizeAsync
中还涉及到一个resource
对象,用来实现面向资源的受权,放在《下一章》中再来介绍,而在本章与《前一章》的示例中,该值均为null
。
ASP.NET Core 中还为IAuthorizationService
提供了几个扩展方法:
public static class AuthorizationServiceExtensions { public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {} public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {} public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {} public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {} }
其默认实现为DefaultAuthorizationService
:
public class DefaultAuthorizationService : IAuthorizationService { private readonly AuthorizationOptions _options; private readonly IAuthorizationHandlerContextFactory _contextFactory; private readonly IAuthorizationHandlerProvider _handlers; private readonly IAuthorizationEvaluator _evaluator; private readonly IAuthorizationPolicyProvider _policyProvider; public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName) { var policy = await _policyProvider.GetPolicyAsync(policyName); return await this.AuthorizeAsync(user, resource, policy); } public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements) { var authContext = _contextFactory.CreateContext(requirements, user, resource); var handlers = await _handlers.GetHandlersAsync(authContext); foreach (var handler in handlers) { await handler.HandleAsync(authContext); if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed) { break; } } return _evaluator.Evaluate(authContext); } }
经过上面代码能够看出,在《上一章》中介绍的受权策略,在这里获取到它的Requirements,后续便再也不须要了。而在AuthorizationService
中是经过调用四大核心对象来完成受权,咱们一一来看。
因为在[Authorize]
中,咱们指定的是策略的名称,所以须要使用IAuthorizationPolicyProvider
来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider
:
public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider { private readonly AuthorizationOptions _options; public Task<AuthorizationPolicy> GetDefaultPolicyAsync() { return Task.FromResult(_options.DefaultPolicy); } public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { return Task.FromResult(_options.GetPolicy(policyName)); } }
在上一章中介绍过,咱们定义的策略都保存在《AuthorizationOptions》的字典中,所以在这里只是简单的将AuthorizationOptions
中的同名方法异步化。
受权上下文是咱们接触较多的对象,当咱们自定义受权Handler时就会用到它,它是使用简单工厂模式来建立的:
public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory { public virtual AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource) { return new AuthorizationHandlerContext(requirements, user, resource); } }
受权上下文中主要包含用户的Claims和受权策略的Requirements:
public class AuthorizationHandlerContext { private HashSet<IAuthorizationRequirement> _pendingRequirements; private bool _failCalled; private bool _succeedCalled; public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource) { Requirements = requirements; User = user; Resource = resource; _pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements); } public virtual bool HasFailed { get { return _failCalled; } } public virtual bool HasSucceeded => !_failCalled && _succeedCalled && !_pendingRequirements.Any(); public virtual void Fail() { _failCalled = true; } public virtual void Succeed(IAuthorizationRequirement requirement) { _succeedCalled = true; _pendingRequirements.Remove(requirement); } }
如上,_pendingRequirements
中保存着全部待验证的Requirements,验证成功的Requirement则从中移除。
兜兜转转,终于进入到了受权的最终验证逻辑中了,首先,使用IAuthorizationHandlerProvider
来获取到全部的受权Handler。
IAuthorizationHandlerProvider
的默认实现为DefaultAuthorizationHandlerProvider
:
public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider { private readonly IEnumerable<IAuthorizationHandler> _handlers; public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers) { _handlers = handlers; } public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context) => Task.FromResult(_handlers); }
在《上一章》中,咱们还介绍到,咱们定义的Requirement,能够直接实现IAuthorizationHandler
接口,也能够单独定义Handler,可是须要注册到DI系统中去。
在默认的AuthorizationHandlerProvider中,会从DI系统中获取到咱们注册的全部Handler,最终调用其HandleAsync
方法。
咱们在实现IAuthorizationHandler
接口时,一般是继承自AuthorizationHandler<TRequirement>
来实现,它有以下定义:
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement { public virtual async Task HandleAsync(AuthorizationHandlerContext context) { foreach (var req in context.Requirements.OfType<TRequirement>()) { await HandleRequirementAsync(context, req); } } protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement); }
如上,首先会在HandleAsync
过滤出与Requirement对匹配的Handler,而后再调用其HandleRequirementAsync
方法。
那咱们定义的直接实现IAuthorizationHandler
了接口的Requirement又是如何执行的呢?
在AddAuthorization
扩展方法中能够看到,默认还为IAuthorizationHandler
注册了一个PassThroughAuthorizationHandler
,定义以下:
public class PassThroughAuthorizationHandler : IAuthorizationHandler { public async Task HandleAsync(AuthorizationHandlerContext context) { foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>()) { await handler.HandleAsync(context); } } }
它负责调用该策略中全部实现了IAuthorizationHandler
接口的Requirement。
最后,经过调用IAuthorizationEvaluator
接口,来完成最终的受权结果,默认实现为DefaultAuthorizationEvaluator
:
public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator { public AuthorizationResult Evaluate(AuthorizationHandlerContext context) => context.HasSucceeded ? AuthorizationResult.Success() : AuthorizationResult.Failed(context.HasFailed ? AuthorizationFailure.ExplicitFail() : AuthorizationFailure.Failed(context.PendingRequirements)); }
当咱们在一个策略中指定多个Requirement时,只有所有验证经过时,受权上下文中的HasSucceeded
才会为True,而HasFailed
表明受权结果的显式失败。
这里根据受权上下文的验证结果来生成受权结果:
public class AuthorizationResult { public bool Succeeded { get; private set; } public AuthorizationFailure Failure { get; private set; } public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true }; public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure }; public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() }; } public class AuthorizationFailure { private AuthorizationFailure() { } public bool FailCalled { get; private set; } public IEnumerable<IAuthorizationRequirement> FailedRequirements { get; private set; } public static AuthorizationFailure ExplicitFail() { return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] }; } public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed) => new AuthorizationFailure { FailedRequirements = failed }; }
整个受权流程的结构大体以下:
经过对 ASP.NET Core 受权系统执行流程的探索,能够看出受权是主要是经过调用IAuthorizationService
来完成的,而受权策略的本质是提供 Requirement ,咱们彻底可使用它们两个来完成各类灵活的受权方式,而不用局限于策略。在 ASP.NET Core 中,还提供了基于资源的受权,放在《下一章》中来介绍,并会简单演示一下在一个通用权限管理系统中如何来受权。