ASP.NET Core Identity 实战(4)授权过程
這篇文章我們將一起來學習 Asp.Net Core 中的(注:這樣描述不準確,稍后你會明白)授權過程
前情提要
在之前的文章里,我們有提到認證和授權是兩個分開的過程,而且認證過程不屬于Identity。同樣授權過程也不屬于Identity,授權過程放在Identity系列中將的原因和認證過程一樣——和成員系統放在一起容易理解。
動手做
在弄清的是授權過程在哪里發生的之前,我們先來動手寫一寫授權的代碼,如果了解策略授權,那么你可以快速瀏覽過這部分
打開之前創建的項目,添加一個名為Demo的控制器,控制器代碼如下:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc;namespace IdentityDemo.Controllers {[Produces("application/json")][Route("api/demo")]public class DemoController : Controller{[Authorize][HttpGet]public object Get(){return new{User.Identity.Name,User.Identity.IsAuthenticated略...用之前注冊的賬戶登錄系統,
訪問/api/demo,你將得到如下結果:
然后退出登錄,再次訪問/api/demo,那么將會跳轉到登陸頁面,在這個過程中Authorize特性起到了至關重要的作用,接下來去掉Authorize特性,重復上兩個操作,未登錄的結果將是:
{"name": null,"isAuthenticated": false }通過這兩個小例子,我們很容易就能推斷出Authorize特性攔截了沒有登陸的用戶,等等,是Authorize特性攔截了請求嗎?
授權過程的發生地
很顯然,不是Authorize特性攔截了請求,特性只是標記了這個方法需要被授權才能訪問,而真正攔截了請求的是——“Mvc 中間件”。Action是由Mvc執行的,Mvc執行時會確認Action上的Authorize特性,來確定是否要進行授權操作(成功授權可以訪問,失敗了會被阻止(比如跳轉到登陸)),以及如何授權(動物園例子中,第二個門衛根據切實的情況決定),也就是自定義授權(角色等等)。
另外,如果我們只是簡單的為 Action方法打上[Authorize]標記,那么它的默認行為就是驗證IsAuthenticated是否是true,也就是在認證環節(Authentication 中間件)是否通過了認證
現在,我們知道了兩個點
- 認證過程 Authentication 發生在 Authentication 中間件中
- 授權過程 Authorization 發生在 Mvc中間件中
基于策略的靈活授權
在企業應用中最為常見的就是基于角色的授權,實現角色授權的方式有兩種,一種是直接寫在Authorize特性上:
[Authorize(Roles = "admin,super-admin,")] [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Test()不過這種方式,不推薦,因為這樣的話我們就將“角色”和“Uri”的綁定“硬編碼在代碼里了”,在很多場景這顯然不合適,所以接下來我們要介紹的基于策略的授權就允許我們自定義授權邏輯,這樣就靈活多了
基于策略Policy的授權
我們假設我們的授權規則是要求和上方代碼片段實現相同效果,即用戶具有角色“admin”或者角色“super-admin”,我們來逐步實現這個目標:
第一步在 DI 中注冊一個用于我們需要的 policy
services.AddAuthorization(options => {options.AddPolicy("role-policy", policy =>{policy.AddRequirements(new RoleRequirement("admin","super-admin"));}); });我們為該策略指定了一個名字role-policy,并且指定了這個策略的需求條件,需求條件主要是為了設置策略的初始值,我們可以在策略注冊時更改需求條件從而靈活控制授權。
接下來我們來編寫 RoleRequirement
public class RoleRequirement : IAuthorizationRequirement {public IEnumerable<string> Roles { get; }public RoleRequirement(params string[] roles){Roles = roles ?? throw new ArgumentNullException(nameof(roles));略...那我們的 RoleRequirement 主要實現的功能就是確定要包含的角色,因為要包含的角色是在構造函數中確定的,那么我們就將角色授權的邏輯(稍后介紹的Handler)和具體授權的數據分開了。
然后我們來實現RoleRequirement對應的處理程序:
public class RoleHandler : AuthorizationHandler<RoleRequirement> {protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement){foreach (var item in requirement.Roles){if (context.User.IsInRole(item)){context.Succeed(requirement);return Task.CompletedTask;}}context.Fail();return Task.CompletedTask;略...這個處理器的工作十分簡單就是驗證當前用戶是否在任意一個由RoleRequirement指定的角色中。在這里context.Succeed(requirement);指示授權成功,而授權失敗一般不需要調用 context.Fail();因為對于這個需求還可能有其它處理器進行處理,而此例中調用 context.Fail();可以確保授權失敗,因為RoleRequirement的處理器只有一個,所以這樣做是沒有問題的。
要注意的是剛剛提到的,我們已經將角色授權的邏輯(稍后介紹的Handler)和具體授權的數據分開了。
因為RoleHandler并不清楚要求用戶有哪些角色,RoleHandler只知道如何去驗證用戶含有哪些角色,而具體要求用戶含有哪些角色,是由 RoleRequirement 來決定的,這符合關注點分離和單一職責這兩個編程概念。
再然后,我們要將剛剛寫好的RoleHandler注冊進Di
services.AddSingleton<IAuthorizationHandler, RoleHandler>();最后一步,更換原來的Attribute:
// [Authorize(Roles = "admin,super-admin,")] [Authorize(Policy ="role-policy")] [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Test()現在,一個最基本的基于策略的授權就完成了。
本文中的示例較為簡單,也并沒有使用全部的授權特性,更詳細的使用方法參考資料很多,本文也就不多做介紹。
另外你可以參考ASP.NET Core中基于策略的授權來學習更過關于策略授權的內容
授權時指定AuthenticationScheme
指定AuthenticationScheme的代碼類似這樣:
// [Authorize(Roles = "admin,super-admin,")] [Authorize(AuthenticationSchemes ="jwt"/*注意,這里的名字取決于你添加AuthenticationHandler時的名字*/, Policy ="role-policy")] [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Test()在上一篇博客ASP.NET Core Identity 實戰(3)認證過程中提到,在Authentication中間件中可以放置多個Handler,而有一個是默認激活的,那么剩下的是被動調用的,現在我們的情況就是由我們在Authorize特性中去挑選一個Handler來執行,例如我們在Authentication中間件上放置兩個Handler——CookieAuthenticationHandler和JwtAuthenticationHandler,并經CookieAuthenticationHandler指定為默認,那么我們想經由Jwt認證時怎么辦?
這里有一個重要問題就是:當HttpContext流過Authentication中間件后才到Mvc中間件,而Mvc在確認Action指定的AuthenticationHandler時,Authentication過程已經結束了。
那這是怎么做到的呢?
還記的HttpContext中有一個擴展方法叫AuthenticateAsync,作為HttpContext的擴展方法也就意味著,我們可以在任何時候調用它進行認證操作。
namespace Microsoft.AspNetCore.Authentication {public static class AuthenticationHttpContextExtensions{public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context);public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme);略...看它的第二個重載,它是指定了 AuthenticationScheme的名字的,所以在Mvc中間件探查到Attribute指定了AuthenticationScheme時,就會重新挑選指定的AuthenticationHandler再次對請求進行認證
授權的發生地——AuthorizationFilter
在舊的Asp.Net時代,我們知道MvcFilter這個東西,現在它仍然在,如果你不了解它,我建議你稍作了解,建議參考官方文檔
正如這一節的標題,授權發生在Microsoft.AspNetCore.Mvc.Authorization.AuthorizationFilter中,授權的邏輯類似這樣:
先進行認證
如果指定了scheme,那么重新認證,如果沒有,則使用之前 Authentication中間件的授權結果:
public virtual async Task<AuthenticateResult> Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.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();}這里面值得再次深入探討的是 context.AuthenticateAsync(scheme),這是在 HttpAbstractions項目中的擴展方法,它的實現是:
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);IAuthenticationService我們在 Authentication中間件中也見過,Authentication中間件也是使用了IAuthenticationService,之前的文章有提到過,這也再次證明了單一原則職責,身份認證中間件負責在管道中認證,而認證本身并非是和身份認證中間件捆綁的,上一篇博客ASP.NET Core Identity 實戰(3)認證過程的最后有認證的源代碼
再進行授權
授權總共分三步
這部分代碼還是很簡單的:
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;}}// 沒了(這主要是對結果進行處理)var result = _evaluator.Evaluate(authContext);if (result.Succeeded){_logger.UserAuthorizationSucceeded(GetUserNameForLogging(user));}else{_logger.UserAuthorizationFailed(GetUserNameForLogging(user));}return result;}這里面和我們在項目中寫的代碼有關就是IAuthorizeHandler的實例,在本文中,我們寫了一個RoleHandler
到此,授權過程就結束了,另外一些就是邊邊角角的知識點,比如授權之后如何操作,這些不難,就不再文中贅述了
總結
以上是生活随笔為你收集整理的ASP.NET Core Identity 实战(4)授权过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ELK之filebeat、logstas
- 下一篇: CF467C George and Jo