在上一章中,詳細介紹了 ASP.NET Core 中的授權策略,在需要授權時,只需要在對應的Controler或者Action上面打上 特性,並指定要執行的策略名稱即可,但是,授權策略是怎麼執行的呢?懷著一顆好奇的心,忍不住來探索一下它的執行流程。 目錄 1. "MVC中的授權" "Authoriz ...
在上一章中,詳細介紹了 ASP.NET Core 中的授權策略,在需要授權時,只需要在對應的Controler或者Action上面打上[Authorize]
特性,並指定要執行的策略名稱即可,但是,授權策略是怎麼執行的呢?懷著一顆好奇的心,忍不住來探索一下它的執行流程。
目錄
在《(上一章》中提到,AuthorizeAttribute
只是一個簡單的實現了IAuthorizeData
介面的特性,並且在 ASP.NET Core 授權系統中並沒有使用到它。我們知道在認證中,還有一個UseAuthentication
擴展方法來激活認證系統,但是在授權中並沒有類似的機制。
這是因為當我們使用[Authorize]
通常是在MVC中,由MVC來負責激活授權系統。本來在這個系列的文章中,我並不想涉及到MVC的知識,但是為了能更好的理解授權系統的執行,就來簡單介紹一下MVC中與授權相關的知識。
MVC中的授權
當我們使用MVC時,首先會調用MVC的AddMvc
擴展方法,用來註冊一些MVC相關的服務:
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
。
AuthorizationApplicationModelProvider
在MVC中有一個ApplicationModel
的概念,它用來封裝Controller
, Filter
, ApiExplorer
等。對應的,在MVC中還提供了一系列的ApplicationModelProvider來初始化ApplicationModel
的各個部分,而AuthorizationApplicationModelProvider
就是用來初始化與授權相關的部分。
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集合中。
其中的關鍵點就是將IAuthorizeData
(也就是通過我們熟悉的[Authorize]
特性)轉化為MVC中的AuthorizeFilter
過濾器:
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的實現。
AuthorizeFilter
在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
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(AuthenticationSchemes)
為什麼還有一個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
認證結果。
AuthorizeAsync(Requirements)
接下來再看一下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; }
}
IAuthorizationService
然後就到了授權的核心對象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
中是通過調用四大核心對象來完成授權,我們一一來看。
IAuthorizationPolicyProvider
由於在[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
中的同名方法非同步化。
IAuthorizationHandlerContextFactory
授權上下文是我們接觸較多的對象,當我們自定義授權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
兜兜轉轉,終於進入到了授權的最終驗證邏輯中了,首先,使用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
最後,通過調用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 中,還提供了基於資源的授權,放在下一章中來介紹,並會簡單演示一下在一個通用許可權管理系統中如何來授權。