使用.NET從零實現(xiàn)基于用戶角色的訪問權限控制
使用.NET從零實現(xiàn)基于用戶角色的訪問權限控制
本文將介紹如何實現(xiàn)一個基于.NET RBAC 權限管理系統(tǒng),如果您不想了解原理,可查看推送的另一篇文章關于Sang.AspNetCore.RoleBasedAuthorization[1] 庫是使用介紹,直接使用該庫即可。
背景
在設計系統(tǒng)時,我們必然要考慮系統(tǒng)使用的用戶,不同的用戶擁有不同的權限。主流的權限管理系統(tǒng)都是RBAC模型(Role-Based Access Control 基于角色的訪問控制)的變形和運用,只是根據(jù)不同的業(yè)務和設計方案,呈現(xiàn)不同的顯示效果。
在微軟文檔中我們了解了《基于角色的授權》[2],但是這種方式在代碼設計之初,就設計好了系統(tǒng)角色有什么,每個角色都可以訪問哪些資源。針對簡單的或者說變動不大的系統(tǒng)來說這些完全是夠用的,但是失去了靈活性。因為我們不能自由的創(chuàng)建新的角色,為其重新指定一個新的權限范圍,畢竟就算為用戶賦予多個角色,也會出現(xiàn)重疊或者多余的部分。
RBAC(Role-Based Access Control)即:基于角色的權限控制。通過角色關聯(lián)用戶,角色關聯(lián)權限的方式間接賦予用戶權限。

RBAC模型可以分為:RBAC0、RBAC1、RBAC2、RBAC3 四種。其中RBAC0是基礎,也是最簡單的,今天我們就先從基礎的開始。
資源描述的管理
在開始權限驗證設計之前我們需要先對系統(tǒng)可訪問的資源進行標識和管理。在后面的權限分配時,我們通過標識好的資源進行資源和操作權限的分配。
資源描述
創(chuàng)建一個 ResourceAttribute 繼承 AuthorizeAttribute 和 IAuthorizationRequirement 資源描述屬性,描述訪問的角色需要的資源要求。通過轉化為 Policy 來對 策略的授權[3] 提出要求。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]public class ResourceAttribute: AuthorizeAttribute, IAuthorizationRequirement{private string _resouceName;private string? _action;/// <summary>/// 設置資源類型/// </summary>/// <param name="name">資源名稱</param>/// <exception cref="ArgumentNullException">資源名稱不能為空</exception>public ResourceAttribute(string name){if (string.IsNullOrEmpty(name)){throw new ArgumentNullException(nameof(name));}string[] resourceValues = name.Split('-');_resouceName = resourceValues[0];if (resourceValues.Length > 1){Action = resourceValues[1];}else{Policy = resourceValues[0];}}/// <summary>/// 獲取資源名稱/// </summary>/// <returns></returns>public string GetResource(){return _resouceName;}/// <summary>/// 獲取操作名稱/// </summary>public string? Action{get{return _action;}set{_action = value;if (!string.IsNullOrEmpty(value)){//把資源名稱跟操作名稱組裝成PolicyPolicy = _resouceName + "-" + value;}}}}
獲得所有資源
我們標識好系統(tǒng)中的資源后,還需要獲取到我們最終程序中都標識有哪些資源,這里就需使用 ASP.NET Core 中的應用程序模型[4]??梢栽诔绦騿訒r獲取到所有的 Controller 和 Controller 中的每一個方法,然后通過查詢 ResourceAttribute 將其統(tǒng)一存儲到靜態(tài)類中。
創(chuàng)建一個 ResourceInfoModelProvider 繼承 IApplicationModelProvider,其執(zhí)行順序我們設置為=> -989。其執(zhí)行順序:
?首先 (Order=-1000):DefaultApplicationModelProvider?然后(Order= -990):AuthorizationApplicationModelProvider CorsApplicationModelProvider?接著是這個 ResourceInfoModelProvider
其核心代碼如下:
/// <summary>/// 基于其 Order 屬性以倒序調用/// </summary>/// <param name="context"></param>public void OnProvidersExecuted(ApplicationModelProviderContext context){if (context == null){throw new ArgumentNullException(nameof(context));}//獲取所有的控制器List<ResourceAttribute> attributeData = new List<ResourceAttribute>();foreach (var controllerModel in context.Result.Controllers){//得到ResourceAttribute//Controller 的特性var resourceData = controllerModel.Attributes.OfType<ResourceAttribute>().ToArray();if (resourceData.Length > 0){attributeData.AddRange(resourceData);}//Controller 中的每個方法的特性foreach (var actionModel in controllerModel.Actions){var actionResourceData = actionModel.Attributes.OfType<ResourceAttribute>().ToArray();if (actionResourceData.Length > 0){attributeData.AddRange(actionResourceData);}}}// 整理信息集中存入全局foreach (var item in attributeData){ResourceData.AddResource(item.GetResource(), item.Action);}}
授權控制的實現(xiàn)
接下來我們要對授權控制來進行編碼實現(xiàn),包含自定義授權策略的實現(xiàn)和自定義授權處理程序。
動態(tài)添加自定義授權策略
關于自定義授權策略提供程序[5]的說明,這里不再贅述微軟的文檔,里面已經介紹了很詳細,這里我們通過其特性可以動態(tài)的創(chuàng)建自定義授權策略,在訪問資源時我們獲取到剛剛標識的 Policy 沒有處理策略,就直接新建一個,并傳遞這個策略的權限檢查信息,當然這只是一方面,更多妙用,閱讀文檔里面其適用范圍的說明即可。
/// <summary>/// 自定義授權策略/// 自動增加 Policy 授權策略/// </summary>/// <param name="policyName">授權名稱</param>/// <returns></returns>public Task<AuthorizationPolicy> GetPolicyAsync(string policyName){// 檢查這個授權策略有沒有AuthorizationPolicy? policy = _options.GetPolicy(policyName);if (policy is null){_options.AddPolicy(policyName, builder =>{builder.AddRequirements(new ResourceAttribute(policyName));});}return Task.FromResult(_options.GetPolicy(policyName));}
授權處理程序
前面我們已經可以動態(tài)創(chuàng)建授權的策略,那么關于授權策略的處理[6]我們可以實現(xiàn) AuthorizationHandler 根據(jù)傳遞的策略處理要求對本次請求進行權限的分析。
internal class ResourceAuthorizationHandler : AuthorizationHandler<ResourceAttribute>{/// <summary>/// 授權處理/// </summary>/// <param name="context">請求上下文</param>/// <param name="requirement">資源驗證要求</param>/// <returns></returns>protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourceAttribute requirement){// 需要有用戶if (context.User is null) return Task.CompletedTask;if (context.User.IsInRole(ResourceRole.Administrator) // 超級管理員權限,擁有 SangRBAC_Administrator 角色不檢查權限|| CheckClaims(context.User.Claims, requirement) // 符合 Resource 或 Resource-Action 組合的 Permission){context.Succeed(requirement);}return Task.CompletedTask;}/// <summary>/// 檢查 Claims 是否符合要求/// </summary>/// <param name="claims">待檢查的claims</param>/// <param name="requirement">檢查的依據(jù)</param>/// <returns></returns>private bool CheckClaims(IEnumerable<Claim> claims, ResourceAttribute requirement){return claims.Any(c =>string.Equals(c.Type, ResourceClaimTypes.Permission, StringComparison.OrdinalIgnoreCase)&& (string.Equals(c.Value, requirement.GetResource(), StringComparison.Ordinal)|| string.Equals(c.Value, $"{requirement.GetResource()}-{requirement.Action}", StringComparison.Ordinal)));}}
這里我們提供了一個內置固定角色名的超級管理員用戶,其請求不進行權限檢查。
最后
這里我們已經實現(xiàn)了簡單的 RBAC 權限設計,之后我們主要在生成 JWT 時帶上可訪問資源的Permission即可。
new Claim(ResourceClaimTypes.Permission,"查詢")當然,如果直接放在 jwt 中會讓 Token 變得很長,雖然我其實并不理解微軟的 ClaimTypes 使用一個URI標識,如果有了解的朋友可以幫我解個惑,萬分感謝 https://stackoverflow.com/questions/72293184/ 。
回到這個問題,我們可以再設計一個中間件,在獲取到用戶的角色名時將其關于角色權限的ClaimTypes加入到 content.User 即可。關于這一方面的詳細介紹和實現(xiàn)可以看下一篇文章。
本文介紹的相關代碼已經提供 Nuget 包,并開源了代碼,感興趣的同學可以查閱:https://github.com/sangyuxiaowu/Sang.AspNetCore.RoleBasedAuthorization
如有錯漏之處,敬請指正。
References
[1] Sang.AspNetCore.RoleBasedAuthorization: https://www.nuget.org/packages/Sang.AspNetCore.RoleBasedAuthorization[2] 《基于角色的授權》: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/roles?view=aspnetcore-6.0[3] 策略的授權: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-6.0[4] 使用 ASP.NET Core 中的應用程序模型: https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/application-model?view=aspnetcore-6.0[5] 自定義授權策略提供程序: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-6.0[6] 授權策略的處理: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-6.0
