在上一章中,詳細介紹了 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 中,還提供了基於資源的受權,放在《下一章》中來介紹,並會簡單演示一下在一個通用權限管理系統中如何來受權。