asp.net core項(xiàng)目授權(quán)流程詳解
在上一篇 聊聊 asp.net core 認(rèn)證和授權(quán) 中我們提到了認(rèn)證和授權(quán)的基本概念,以及認(rèn)證和授權(quán)的關(guān)系及他們之間的協(xié)同工作流程,在這篇文章中,我將通過分析asp.net core 3.1 授權(quán)流程的源碼給大家介紹asp.net core 框架里面授權(quán)流程的具體實(shí)現(xiàn)邏輯,本文并非講解具體的實(shí)戰(zhàn)應(yīng)用,建議在使用過asp.net core 授權(quán)框架后在來閱讀本文收貨會更多。
一、授權(quán)流程用到的主要的幾個接口及類
- IAuthorizationService,默認(rèn)實(shí)現(xiàn)類: DefaultAuthorizationService,該類主要職責(zé)就是遍歷所有注入到容器的實(shí)現(xiàn)了IAuthorizationHandler接口的服務(wù),并調(diào)用其HandleAsync方法來進(jìn)行授權(quán)檢查,也就是說該類的主要職責(zé)就是檢查授權(quán)策略(AuthorizationPolicy)是否校驗(yàn)通過,校驗(yàn)通過則授權(quán)成功,否則授權(quán)失敗。
- IAuthorizationPolicyProvider,默認(rèn)實(shí)現(xiàn)類:DefaultAuthorizationPolicyProvider,負(fù)責(zé)根據(jù)策略名稱提供授權(quán)策略,以及提供默認(rèn)授權(quán)策略等,內(nèi)部就是從AuthorizationOptions內(nèi)部的策略字典(Dictionary)中直接獲取。
- IAuthorizationHandlerProvider,默認(rèn)實(shí)現(xiàn)類:DefaultAuthorizationHandlerProvider,用于獲取已經(jīng)注冊到容器中的所有實(shí)現(xiàn)了IAuthorizationHandler的授權(quán)服務(wù),所有授權(quán)服務(wù)是通過構(gòu)造函數(shù)依賴注入實(shí)現(xiàn)的(IEnumerable<IAuthorizationHandler>作為構(gòu)造函數(shù)入?yún)ⅲ?/li>
- IAuthorizationHandler,默認(rèn)實(shí)現(xiàn)類:PassThroughAuthorizationHandler,該類是AddAuthorization的時(shí)候默認(rèn)注冊的授權(quán)處理程序(實(shí)現(xiàn)IAuthorizationHandler接口),用于遍歷授權(quán)策略中包含的所有的實(shí)現(xiàn)了IAuthorizationHandler的Requirement類,并調(diào)用其HandleAsync方法進(jìn)行檢查Requirement授權(quán)是否成功,這里的Requirement類是指實(shí)現(xiàn)了AuthorizationHandler<TRequirement>抽象基類的Requirement類。
- IAuthorizationEvaluator,默認(rèn)實(shí)現(xiàn)類:DefaultAuthorizationEvaluator,執(zhí)行授權(quán)流程,并對授權(quán)檢查結(jié)果進(jìn)行檢查,如果是授權(quán)失敗,并且未認(rèn)證則返回401,如果是授權(quán)失敗,但認(rèn)證通過,則返回403
- IAuthorizationHandlerContextFactory,默認(rèn)實(shí)現(xiàn)類:DefaultAuthorizationHandlerContextFactory,用于創(chuàng)建AuthorizationHandlerContext對象的工廠類,AuthorizationHandlerContext 上下文中包含每次授權(quán)流程中要被校驗(yàn)的所有的Requirement類。
- AuthorizationMiddleware,負(fù)責(zé)對請求進(jìn)行授權(quán)檢查的中間件.
- AuthorizationOptions類,內(nèi)部維護(hù)了一個策略字典(Dictionary)用于存儲所有注冊的策略,key為策略名稱,value為具體的策略(AuthorizationPolicy)
- AuthorizationPolicy類,策略的具體表示,主要包含 AuthenticationSchemes 和 Requirements屬性,AuthenticationSchemes 表示執(zhí)行該策略時(shí)采用什么認(rèn)證方案進(jìn)行身分認(rèn)證, Requirements 表示該策略要驗(yàn)證的Requirement列表
- AuthorizationPolicyBuilder類,該類主要是用于構(gòu)建AuthorizationPolicy類,也就是用于構(gòu)建具體策略的類,通過該類,可以指定該授權(quán)策略需要采用什么認(rèn)證方案進(jìn)行認(rèn)證,以及授權(quán)檢查時(shí)需要滿足那些Requirement。
二、授權(quán)服務(wù)注冊流程
首先找到 PolicyServiceCollectionExtensions 類,這個擴(kuò)展方法類,對IServiceCollection接口進(jìn)行了擴(kuò)展,因此我們可以在Startup.cs 的ConfigureService方法中直接
services.AddAuthorization來注冊 授權(quán)相關(guān)服務(wù)。
// Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public static class PolicyServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
services.TryAddSingleton<AuthorizationPolicyMarkerService>();
services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());
return services;
}
//當(dāng)不想在應(yīng)用程序中注冊授權(quán)策略時(shí),直接調(diào)用此方法即可。
public static IServiceCollection AddAuthorization(this IServiceCollection services)
{
return services.AddAuthorization(null);
}
//當(dāng)需要在應(yīng)用程序中注冊特定的授權(quán)策略時(shí),調(diào)用這個方法,configure為Action類型的委托方法,入?yún)锳uthorizationOptions 授權(quán)配置類,
//可通過該類的AddPolicy方法來進(jìn)行授權(quán)策略的注冊。
public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
services.AddAuthorizationCore(configure);
services.AddAuthorizationPolicyEvaluator();
return services;
}
}可以看到,內(nèi)部調(diào)用了AddAuthorizationCore方法,這個擴(kuò)展方法定義在:AuthorizationServiceCollectionExtensions 類
// Microsoft.Extensions.DependencyInjection.AuthorizationServiceCollectionExtensions
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public static class AuthorizationServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
//以下這些服務(wù)便是上文中介紹的授權(quán)流程用到的主要服務(wù)類,及具體的默認(rèn)實(shí)現(xiàn)類。
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
return services;
}
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
//這里的configure便是我們應(yīng)用程序傳入的委托回調(diào)方法,用于向AuthorizationOptions類添加授權(quán)策略。
if (configure != null)
{
services.Configure(configure);
}
return services.AddAuthorizationCore();
}
}下面這個是應(yīng)用注冊授權(quán)策略的常規(guī)流程的一個例子:
public void ConfigureServices(IServiceCollection services)
{
//添加授權(quán)相關(guān)服務(wù)。
services.AddAuthorization(options =>
{
//往AuthorizationOptions類中添加名為:adminPolicy的授權(quán)策略。
//參數(shù):authorizationPolicyBuilder 為AuthorizationPolicyBuilder類。
options.AddPolicy("adminPolicy", authorizationPolicyBuilder =>
{
authorizationPolicyBuilder.AddAuthenticationSchemes("Cookie");
//表示用戶必須屬于admin角色才能訪問。
authorizationPolicyBuilder.AddRequirements(new RolesAuthorizationRequirement(new string[] { "admin" }));
//表示用戶聲明中包含名為cardNo的 Claim,并且值為23902390才允許訪問,也就是 HttpContext.User.Claims 中包含cardNo,并且值為相應(yīng)值才能訪問。
authorizationPolicyBuilder.Requirements.Add(new ClaimsAuthorizationRequirement("cardNo", new string[] { "23902390" }));
//表示用用戶名必須是admin才允許訪問,AuthorizationBuilder中海油RequireClaim、RequireRole等方法。
authorizationPolicyBuilder.RequireUserName("admin");
//只有以上3個Requirement同時(shí)滿足,該策略才算授權(quán)成功
});
});
}三、啟用授權(quán)流程
第二個步驟僅僅是將授權(quán)流程中用到的相關(guān)服務(wù)注冊到依賴注入容器中,以及應(yīng)用配置授權(quán)策略,真正的啟用授權(quán)流程則需要通過 Startup.cs 類中的Configure方法中調(diào)用 app.UseAuthorization(); 進(jìn)行開啟,本質(zhì)上就是將 AuthorizationMiddleware 授權(quán)中間件,注冊到中間件管道中。
// Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Builder;
public static class AuthorizationAppBuilderExtensions
{
public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
VerifyServicesRegistered(app);
//注冊授權(quán)中間件。AuthorizationMiddleware
return app.UseMiddleware<AuthorizationMiddleware>(Array.Empty<object>());
}
private static void VerifyServicesRegistered(IApplicationBuilder app)
{
if (app.ApplicationServices.GetService(typeof(AuthorizationPolicyMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatException_UnableToFindServices("IServiceCollection", "AddAuthorization", "ConfigureServices(...)"));
}
}
}要看授權(quán)流程的具體執(zhí)行邏輯,我們還是要看AuthorizationMiddleware類。
// Microsoft.AspNetCore.Authorization.AuthorizationMiddleware
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
public class AuthorizationMiddleware
{
private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked";
private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object();
private readonly RequestDelegate _next;
private readonly IAuthorizationPolicyProvider _policyProvider;
public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
{
_next = next ?? throw new ArgumentNullException("next");
_policyProvider = policyProvider ?? throw new ArgumentNullException("policyProvider");
}
public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
Endpoint endpoint = context.GetEndpoint();
if (endpoint != null)
{
context.Items["__AuthorizationMiddlewareWithEndpointInvoked"] = AuthorizationMiddlewareWithEndpointInvokedValue;
}
//這里獲取Controller或者Action上標(biāo)注的一個或者多個[Authorize]特性,
//每個Authorize特性都有一個Policy屬性,用于指定一個或者多個授權(quán)策略,表示這些策略必須同時(shí)滿足才算授權(quán)通過,
//Roles屬性則用于指定用戶角色列表,表示用戶必須屬于這些角色才允許訪問,這里的角色控制最終其實(shí)也是轉(zhuǎn)換為策略的形式去控制。
//AuthenticationSchemes則用于指定認(rèn)證方案列表,表示用戶訪問該資源時(shí)采用這些認(rèn)證方案進(jìn)行身份認(rèn)證
//如:[Authorize(AuthenticationSchemes = "cookie", Policy = "adminPolicy", Roles = "admin")]
IReadOnlyList<IAuthorizeData> authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
//以下將Controller或者Action上的一個或者多個[Authorize]特性上指定的訪問該資源所需要的滿足的Policy授權(quán)策略列表,
//及訪問該資源時(shí)用戶所需具備的角色列表,以及訪問該資源時(shí)將采用的認(rèn)證方案合并到一個策略對象中去,
//也就是說最終返回的這個授權(quán)策略包含了訪問該資源所需要滿足的所有授權(quán)策略列表,用戶所必須具備的所有用戶角色列表,以及采用的所有認(rèn)證方案列表。
AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
if (policy == null)
{
await _next(context);
return;
}
IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
//這里首先對當(dāng)前訪問者進(jìn)行用戶身份的認(rèn)證,認(rèn)證方案采用的是上面合并過后的一個或者多個認(rèn)證方案進(jìn)行認(rèn)證。
AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context);
//如果允許匿名訪問,則不再進(jìn)行授權(quán)檢查。
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
}
//這里對policy中包含的所有授權(quán)策略進(jìn)行一一檢查,如果全部驗(yàn)證通過,則表示授權(quán)成功,允許用戶訪問,
//否則根據(jù)用戶是否已經(jīng)登錄來判定是讓用戶登錄(401-Challenged)還是提示用戶沒權(quán)限訪問(403-Forbiden)
PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint);
if (policyAuthorizationResult.Challenged)
{
//如果授權(quán)失敗,且用戶身份未認(rèn)證,且指定了認(rèn)證方案,則調(diào)用特定的認(rèn)證方案的Chanllege方法。
if (policy.AuthenticationSchemes.Any())
{
foreach (string authenticationScheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(authenticationScheme);
}
}
//如果該資源沒有指定任何認(rèn)證方案,則采用默認(rèn)的認(rèn)證方案。
else
{
await context.ChallengeAsync();
}
}
else if (policyAuthorizationResult.Forbidden)
{
//如果授權(quán)失敗,且用戶身份已認(rèn)證,且指定了認(rèn)證方案,則調(diào)用特定的認(rèn)證方案的Forbid方法來處理禁止訪問的處理邏輯。
if (policy.AuthenticationSchemes.Any())
{
foreach (string authenticationScheme2 in policy.AuthenticationSchemes)
{
await context.ForbidAsync(authenticationScheme2);
}
}
//如果該資源沒有指定任何認(rèn)證方案,則采用默認(rèn)的認(rèn)證方案來處理禁止訪問的邏輯
else
{
await context.ForbidAsync();
}
}
else
{
await _next(context);
}
}
}以下是AuthorizationPolicy.CombineAsync方法的詳細(xì)說明,該方法主要是用于將一個或者多個Authorize特性指定的授權(quán)策略,用戶角色列表,認(rèn)證方案進(jìn)行合并,最終返回一個授權(quán)策略對象,這個授權(quán)策略包含了 訪問該資源所需用到的所有認(rèn)證方案,所有必須滿足的Requirement.
// Microsoft.AspNetCore.Authorization.AuthorizationPolicy
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
{
if (policyProvider == null)
{
throw new ArgumentNullException("policyProvider");
}
if (authorizeData == null)
{
throw new ArgumentNullException("authorizeData");
}
bool flag = false;
IList<IAuthorizeData> list = authorizeData as IList<IAuthorizeData>;
if (list != null)
{
flag = list.Count == 0;
}
AuthorizationPolicyBuilder policyBuilder = null;
if (!flag)
{
//這里遍歷Controller或者Action上的一個或者多個[Authorize]特性
foreach (IAuthorizeData authorizeDatum in authorizeData)
{
if (policyBuilder == null)
{
policyBuilder = new AuthorizationPolicyBuilder();
}
bool flag2 = true;
//如果某個[Authorize]特性有指定授權(quán)策略,則將該授權(quán)策略添加到合并列表中。
if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
{
//IAuthorizationPolicyPovider 內(nèi)部其實(shí)就是讀取 AuthorizationOptions的字典屬性中保存的策略,key為策略名稱,value為相應(yīng)的授權(quán)策略。
AuthorizationPolicy authorizationPolicy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
if (authorizationPolicy == null)
{
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
}
//其實(shí)就是將 Requirements 和 AuthenticationSchemes(認(rèn)證方案列表) 添加到合并后的Requirements及授權(quán)方案列表中去。
policyBuilder.Combine(authorizationPolicy);
flag2 = false;
}
string[] array = authorizeDatum.Roles?.Split(',');
if (array != null && array.Any())
{
IEnumerable<string> roles = from r in array
where !string.IsNullOrWhiteSpace(r)
select r.Trim();
//如果一個[Authorize]特性指定了Roles屬性,那么將屬性中指定的一個或者多個角色列表添加到合并后的角色列表中去。
//看RequireRole,其實(shí)就是往合并后的Requirements中添加了一個名為:RolesAuthorizationRequirement的Requirement
policyBuilder.RequireRole(roles);
flag2 = false;
}
string[] array2 = authorizeDatum.AuthenticationSchemes?.Split(',');
if (array2 != null && array2.Any())
{
string[] array3 = array2;
//將Authorize特性中指定的一個或者多個認(rèn)證方案添加到合并后的認(rèn)證方案列表中。
foreach (string text in array3)
{
if (!string.IsNullOrWhiteSpace(text))
{
policyBuilder.AuthenticationSchemes.Add(text.Trim());
}
}
}
//如果當(dāng)前Authorize特性既沒有指定授權(quán)策略,也沒有指定角色列表,那么采用默認(rèn)授權(quán)策略(默認(rèn)授權(quán)策略其實(shí)就是要求用戶身份必須被認(rèn)證通過)
if (flag2)
{
AuthorizationPolicyBuilder authorizationPolicyBuilder = policyBuilder;
authorizationPolicyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
}
}
}
//如果一個Controller或者Action沒有指定任何[Authorize]特性,那么如果啟用了授權(quán)流程,則采用Fallback策略進(jìn)行授權(quán)檢查。
if (policyBuilder == null)
{
AuthorizationPolicy authorizationPolicy2 = await policyProvider.GetFallbackPolicyAsync();
if (authorizationPolicy2 != null)
{
return authorizationPolicy2;
}
}
return policyBuilder?.Build();
}以下是對 IPolicyEvaluator.AuthenticateAsync方法的說明,該方法主要是對訪問該資源所指定的認(rèn)證方案列表進(jìn)行一一認(rèn)證,并將認(rèn)證結(jié)果產(chǎn)生的用戶信息進(jìn)行合并,默認(rèn)實(shí)現(xiàn)類是:PolicyEvaluator,該接口主要定義了兩個方法,一個是:AuthenticateAsync,負(fù)責(zé)對當(dāng)前訪問者進(jìn)行身份認(rèn)證,一個是AuthorizeAsync,負(fù)責(zé)對當(dāng)前訪問者進(jìn)行授權(quán)檢查,通常要授權(quán)成功,必須要求用戶先進(jìn)行身份認(rèn)證,認(rèn)證通過并且授前檢查通過才允許訪問,但認(rèn)證不是必須的,如果你要自定義授權(quán)邏輯的話,你甚至可以不認(rèn)證用戶身份也授權(quán)其進(jìn)行訪問,但實(shí)際開發(fā)中通常不會這么做,這里僅僅只是闡述兩者之間的一些聯(lián)系,之所以默認(rèn)標(biāo)記了Authorize特性并且啟用授權(quán)流程后,要求用戶必須登錄(身份認(rèn)證)是因?yàn)橛肹Authorize]特性標(biāo)記控制器后,執(zhí)行的是默認(rèn)策略,而默認(rèn)策略就是必須要求用戶進(jìn)行身份認(rèn)證。
// Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal;
public class PolicyEvaluator : IPolicyEvaluator
{
private readonly IAuthorizationService _authorization;
public PolicyEvaluator(IAuthorizationService authorization)
{
_authorization = authorization;
}
//參數(shù)policy是一個合并后的策略,里面包含了訪問該資源所采用的所有認(rèn)證方案列表。
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
{
ClaimsPrincipal newPrincipal = null;
//如果被訪問的資源指定了身份認(rèn)證方案,則采用指定的身份認(rèn)證方案一一進(jìn)行認(rèn)證,并把所有身份認(rèn)證結(jié)果進(jìn)行合并。
//認(rèn)證流程中添加的一個或者多個認(rèn)證方案,可以在授權(quán)流程中被調(diào)用進(jìn)行用戶身份的認(rèn)證,雖然一個應(yīng)用可以添加多個認(rèn)證方案,
//但默認(rèn)情況下,認(rèn)證流程只會調(diào)用默認(rèn)的認(rèn)證方案進(jìn)行身份認(rèn)證。
foreach (string authenticationScheme in policy.AuthenticationSchemes)
{
AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme);
if (authenticateResult != null && authenticateResult.Succeeded)
{
newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, authenticateResult.Principal);
}
}
if (newPrincipal != null)
{
context.User = newPrincipal;
return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
}
context.User = new ClaimsPrincipal(new ClaimsIdentity());
return AuthenticateResult.NoResult();
}
//如果當(dāng)前被訪問的資源沒有指定采用何種認(rèn)證方案進(jìn)行身份認(rèn)證,則默認(rèn)采用認(rèn)證流程產(chǎn)生的身份認(rèn)證信息。
return (context.User?.Identity?.IsAuthenticated).GetValueOrDefault() ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult();
}
//這個是對合并后的授權(quán)策略進(jìn)行授權(quán)檢查的方法,內(nèi)部還是去調(diào)用了IAuthorizationService.AuthorizeAsync方法。
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
if (policy == null)
{
throw new ArgumentNullException("policy");
}
if ((await _authorization.AuthorizeAsync(context.User, resource, policy)).Succeeded)
{
return PolicyAuthorizationResult.Success();
}
return authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();
}
}以下是IAuthorizationService.AuthorizeAsync的說明,主要負(fù)責(zé)對合并后的授權(quán)策略(AuthorizationPolicy)中的Requirements進(jìn)行一一檢查,全部檢查通過,則授權(quán)成功,默認(rèn)實(shí)現(xiàn)類是:DefaultAuthorizationService
// Microsoft.AspNetCore.Authorization.DefaultAuthorizationService
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public class DefaultAuthorizationService : IAuthorizationService
{
private readonly AuthorizationOptions _options;
private readonly IAuthorizationHandlerContextFactory _contextFactory;
private readonly IAuthorizationHandlerProvider _handlers;
private readonly IAuthorizationEvaluator _evaluator;
private readonly IAuthorizationPolicyProvider _policyProvider;
private readonly ILogger _logger;
public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IAuthorizationHandlerProvider handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options)
{
if (options == null)
{
throw new ArgumentNullException("options");
}
if (policyProvider == null)
{
throw new ArgumentNullException("policyProvider");
}
if (handlers == null)
{
throw new ArgumentNullException("handlers");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
if (contextFactory == null)
{
throw new ArgumentNullException("contextFactory");
}
if (evaluator == null)
{
throw new ArgumentNullException("evaluator");
}
_options = options.Value;
_handlers = handlers;
_policyProvider = policyProvider;
_logger = logger;
_evaluator = evaluator;
_contextFactory = contextFactory;
}
//這個就是檢查授權(quán)策略的核心邏輯了,流程就是讀取 依賴注入容器中所有注冊的實(shí)現(xiàn)了IAuthorizationHandler接口的服務(wù),并對其遍歷并分別調(diào)用服務(wù)的HandleAsync方法。
//微軟默認(rèn)注入的IAuthorizationHandler的實(shí)現(xiàn)類是: PassThroughAuthorizationHandler,該類主要是找出Requirements中實(shí)現(xiàn)了IAuthorizationHandler的Requirement類,并對其調(diào)用HandleAsync方法來檢查這類Requirement是否授權(quán)通過。
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
if (requirements == null)
{
throw new ArgumentNullException("requirements");
}
//AuthorizationHandlerContext 上下文中,包含了所有需要進(jìn)行授權(quán)檢查的Requirement。
AuthorizationHandlerContext authContext = _contextFactory.CreateContext(requirements, user, resource);
foreach (IAuthorizationHandler item in await _handlers.GetHandlersAsync(authContext))
{
await item.HandleAsync(authContext);
//如果授權(quán)檢查失敗,并且InvokeHandlersAfterFailure為false時(shí),即某一個Requirement檢查失敗時(shí),是否繼續(xù)執(zhí)行剩余的Requirement檢查。
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
{
break;
}
}
//這里主要是檢查是否所有的Requirement都驗(yàn)證通過,如果都驗(yàn)證通過,那么返回授權(quán)成功,否則返回授權(quán)失敗。
AuthorizationResult authorizationResult = _evaluator.Evaluate(authContext);
if (authorizationResult.Succeeded)
{
_logger.UserAuthorizationSucceeded();
}
else
{
_logger.UserAuthorizationFailed();
}
return authorizationResult;
}
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)
{
if (policyName == null)
{
throw new ArgumentNullException("policyName");
}
AuthorizationPolicy authorizationPolicy = await _policyProvider.GetPolicyAsync(policyName);
if (authorizationPolicy == null)
{
throw new InvalidOperationException("No policy found: " + policyName + ".");
}
return await this.AuthorizeAsync(user, resource, authorizationPolicy);
}
}以下是IAuthorizationEvaluator的默認(rèn)實(shí)現(xiàn)類:DefaultAuthorizationEvaluator的源碼,負(fù)責(zé)檢查是否所有Requirement類都驗(yàn)證通過,如果存在部分未驗(yàn)證通過,則返回授權(quán)失敗。
// Microsoft.AspNetCore.Authorization.DefaultAuthorizationEvaluator
using Microsoft.AspNetCore.Authorization;
public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
{
public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
{
//看HasSucceded源碼,其實(shí)要授權(quán)成功,必須沒有顯式調(diào)用授權(quán)失敗的方法。
if (!context.HasSucceeded)
{
return AuthorizationResult.Failed(context.HasFailed ? AuthorizationFailure.ExplicitFail() : AuthorizationFailure.Failed(context.PendingRequirements));
}
return AuthorizationResult.Success();
}
}以下是:AuthorizationHandlerContext的源碼
// Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
public class AuthorizationHandlerContext
{
private HashSet<IAuthorizationRequirement> _pendingRequirements;
private bool _failCalled;
private bool _succeedCalled;
public virtual IEnumerable<IAuthorizationRequirement> Requirements
{
get;
}
public virtual ClaimsPrincipal User
{
get;
}
public virtual object Resource
{
get;
}
public virtual IEnumerable<IAuthorizationRequirement> PendingRequirements => _pendingRequirements;
public virtual bool HasFailed => _failCalled;
public virtual bool HasSucceeded
{
get
{
if (!_failCalled && _succeedCalled)
{
return !PendingRequirements.Any();
}
return false;
}
}
public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)
{
if (requirements == null)
{
throw new ArgumentNullException("requirements");
}
Requirements = requirements;
User = user;
Resource = resource;
_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
}
//如果調(diào)用了此方法,那么直接進(jìn)入授權(quán)失敗流程了,也就是顯式告訴應(yīng)用授權(quán)失敗了。
public virtual void Fail()
{
_failCalled = true;
}
//某個Requirement驗(yàn)證成功,那么將會調(diào)用該方法,并從未驗(yàn)證的Requirements列表中移除。
public virtual void Succeed(IAuthorizationRequirement requirement)
{
_succeedCalled = true;
_pendingRequirements.Remove(requirement);
}
}以下是:PassThroughAuthorizationHandler的源碼,邏輯比較簡單,就是讀取Requirements中所有實(shí)現(xiàn)了IAuthorizationHandler接口的Requirement類,并調(diào)用HandleAsync方法,這就是為什么我們在[Authrize(Roles="admin")]特性中指定角色列表的時(shí)候,并在 AuthorizationPolicy.CombineAsync 中被動態(tài)合并到策略對象中后,能被執(zhí)行的原因,Roles屬性指定的角色列表最終會被動態(tài)轉(zhuǎn)換成:RolesAuthorizationRequirement,并將這個Requirement合并到最終的策略中去,微軟 Microsoft.AspNetCore.Authorization.Infrastructure 命名空間下提供了 ClaimsAuthorizationRequirement 、DenyAnonymousAuthorizationRequirement 等Requirement類,其中 DenyAnonymousAuthorizationRequirement 就是默認(rèn)策略所包含的Requirement,也就是要求用戶必須登錄進(jìn)行身份認(rèn)證后才能進(jìn)行訪問,如果被訪問的資源未指定授權(quán)策略的情況下。
// Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
public async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationHandler item in context.Requirements.OfType<IAuthorizationHandler>())
{
await item.HandleAsync(context);
}
}
}以下是RolesRequirement類的源碼,表示用戶必須屬于指定角色才能進(jìn)行訪問特定資源,HandleRequirementAsync被AuthorizationHandler抽象基類中的HandleAsync方法調(diào)用,基類中的HandleAsync則是找出訪問授權(quán)策略中所有屬于該類型的Requirement,然后分別調(diào)用其 HandleRequirementAsync方法。
// Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public IEnumerable<string> AllowedRoles
{
get;
}
public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
{
if (allowedRoles == null)
{
throw new ArgumentNullException("allowedRoles");
}
if (allowedRoles.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
}
AllowedRoles = allowedRoles;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
{
if (context.User != null)
{
bool flag = false;
if (requirement.AllowedRoles != null && requirement.AllowedRoles.Any())
{
flag = requirement.AllowedRoles.Any((string r) => context.User.IsInRole(r));
}
if (flag)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}以下是應(yīng)用開啟授權(quán)流程的一個示例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
//啟用認(rèn)證流程。
app.UseAuthentication();
//啟用授權(quán)流程
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
//RequireAuthorization表示所有Controller都需要登錄后才能訪問。
endpoints.MapDefaultControllerRoute().RequireAuthorization();
});
}總結(jié)來說,授權(quán)流程首先就是 讀取 Controller 或者 Action 上指定的一個或者多個 [Authorize] 特性,并把這些特性指定的授權(quán)策略中所包含的Requirement類(實(shí)現(xiàn)了IAuthorizationRequirement接口的類)統(tǒng)一合并到一個策略對象中去,對于未指定具體策略的[Authorize]特性,則采用默認(rèn)的授權(quán)策略(要求用戶必須登錄認(rèn)證),同時(shí)也把這些特性中指定的認(rèn)證方案進(jìn)行統(tǒng)一合并到一個策略對象中去,然后對當(dāng)前用戶對合并后的策略中所包含的認(rèn)證方案一一進(jìn)行身份認(rèn)證,并將身份認(rèn)證結(jié)果進(jìn)行一一合并,然后就是對合并后的授權(quán)策略中的Requirement一一進(jìn)行檢查,如果全部授權(quán)通過,并且沒有顯式調(diào)用授權(quán)失敗的方法,則授權(quán)成功。
到此這篇關(guān)于asp.net core授權(quán)流程的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ASP.NET開發(fā)者使用jQuery應(yīng)該了解的幾件事情
如果你是有著APS.NET開發(fā)背景的人員,那么jQuery的幾個概念建議你應(yīng)該忘掉。像使用其它的framework一樣,你應(yīng)該學(xué)習(xí)一下jQuery的所有語法等約定來讓它更好的為你服務(wù)。2009-09-09
調(diào)試ASP.NET應(yīng)用程序的方法和技巧
調(diào)試ASP.NET應(yīng)用程序的方法和技巧...2006-09-09
.NET Core 處理 WebAPI JSON 返回?zé)┤说膎ull為空
這篇文章主要介紹了.NET Core 處理 WebAPI JSON 返回?zé)┤说膎ull為空,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
asp.net模板引擎Razor調(diào)用外部方法用法實(shí)例
這篇文章主要介紹了asp.net模板引擎Razor調(diào)用外部方法用法,實(shí)例分析了Razor調(diào)用外部方法的相關(guān)使用技巧,需要的朋友可以參考下2015-06-06
ASP.NET的事件模型(很適合學(xué)習(xí)的文章)
當(dāng)我們新建一個ASP.NET的應(yīng)用程序時(shí),會默認(rèn)生成一個Default.aspx和Default.aspx.cs頁面2012-10-10

