基於ABP框架實現RBAC(角色訪問控制)
在業務系統需求規劃過程中,通常對於諸如組織機構、使用者和角色等這種基礎功能,通常是將這部分功能規劃到通用子域中,這也說明了,對於這部分功能來講,是系統的基石,整個業務體系是建立於這部分基石之上的,當然,還有諸如多語言、設定管理、認證和授權等。對於這部分功能,ABP中存在這些概念,並且通過Module Zero模組完成了這些概念。
一、角色訪問控制之RBAC
RBAC:Role Based Access Control,基於角色的訪問控制,這在目前大多數軟體中來講已經算得上是普遍應用了,最常見的結構如下,結構簡單,設計思路清晰。
但是也存在其它升級版的設計,諸如使用者許可權表、角色組、使用者組的概念等,具體分類有RBAC0、RBAC1、RBAC2等,後者功能越來越強大,也越來越複雜。
- RBAC0:是RBAC的核心思想。
- RBAC1:是把RBAC的角色分層模型。
- RBAC2:增加了RBAC的約束模型。
- RBAC3:整合RBAC2 + RBAC1。
二、ABP中的RBAC
在Abp中,已經集成了這些概念,並在ModuleZero模組中實現了這些概念,基於IdentityServer4的ModuleZero模組完成了封裝。對於我們大多數以業務為中心的開發人員來講,不應該又去造一個輪子,而是應該開好這輛車。首先看下Abp中的RBAC模型
在這其中許可權表中記錄了使用者與許可權,角色與許可權兩部分。對於許可權通常指的是功能許可權和資料許可權兩部分,一般來講,大多指的是功能許可權,這種通過角色與許可權進行管理即可,如還有使用者部分的功能區分,則可以再使用上使用者與許可權,而對於資料許可權,可以利用使用者與許可權部分,個人用的比較少,但是,可以想象到這麼一個場景,針對於一家門店內的多個店長,角色相同即相應的許可權相同,但各自關心的資料來源不同,關心東部、南部等資料,而不關心西部、北部資料,因此可以在資料層面進行劃分,比如設定資料來源,東南西北,對於資料來源進行許可權關聯,這樣一來使用者本身如果擁有東部資料許可權,則只能看到東部資料。
1、許可權宣告及應用
在Abp中,需要首先在Core層/Authorization/PermissionNames.cs中宣告許可權,Abp許可權部分設計原則是:先宣告再使用
。
/// <summary> /// 許可權命名 /// </summary> public static class PermissionNames { #region 頂級許可權 public const string Pages = "Pages"; #endregion #region 基礎支撐平臺 public const string Pages_Frame = "Pages.Frame"; #region 租戶管理 public const string Pages_Frame_Tenants = "Pages.Frame.Tenants"; #endregion #region 組織機構 public const string Pages_Frame_OrganizationUnits = "Pages.Frame.OrganizationUnits"; public const string Pages_Frame_OrganizationUnits_Create = "Pages.Frame.OrganizationUnits.Create"; public const string Pages_Frame_OrganizationUnits_Update = "Pages.Frame.OrganizationUnits.Update"; public const string Pages_Frame_OrganizationUnits_Delete = "Pages.Frame.OrganizationUnits.Delete"; #endregion #region 使用者管理 public const string Pages_Frame_Users = "Pages.Frame.Users"; public const string Pages_Frame_Users_Create = "Pages.Frame.Users.Create"; public const string Pages_Frame_Users_Update = "Pages.Frame.Users.Update"; public const string Pages_Frame_Users_Delete = "Pages.Frame.Users.Delete"; public const string Pages_Frame_Users_ResetPassword = "Pages.Frame.Users.ResetPassword"; #endregion #region 角色管理 public const string Pages_Frame_Roles = "Pages.Roles"; public const string Pages_Frame_Roles_Create = "Pages.Frame.Roles.Create"; public const string Pages_Frame_Roles_Update = "Pages.Frame.Roles.Update"; public const string Pages_Frame_Roles_Delete = "Pages.Frame.Roles.Delete"; #endregion }
然後在Core層/Authorization/XXXAuthorizationProvider.cs中設定具體許可權,在此處設定許可權時,可以根據許可權設計時候的職責劃分,比如如果僅僅是多租戶需要這部分,那便設定許可權範圍為多租戶即可。
public class SurroundAuthorizationProvider : AuthorizationProvider { public override void SetPermissions(IPermissionDefinitionContext context) { #region 頂級許可權 var pages = context.CreatePermission(PermissionNames.Pages,L("Pages")); #endregion #region 基礎支撐平臺 var frame = pages.CreateChildPermission(PermissionNames.Pages_Frame,L("Frame")); #region 租戶管理 frame.CreateChildPermission(PermissionNames.Pages_Frame_Tenants,L("Tenants"),multiTenancySides: MultiTenancySides.Host); #endregion #region 組織機構 var organizationUnits = frame.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits,L("OrganizationUnits")); organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Create,L("CreateOrganizationUnit")); organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Update,L("EditOrganizationUnit")); organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Delete,L("DeleteOrganizationUnit")); #endregion #region 使用者管理 var users = frame.CreateChildPermission(PermissionNames.Pages_Frame_Users,L("Users")); users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Create,L("CreateUser")); users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Update,L("UpdateUser")); users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Delete,L("DeleteUser")); users.CreateChildPermission(PermissionNames.Pages_Frame_Users_ResetPassword,L("ResetPassword")); #endregion #region 角色管理 var roles = frame.CreateChildPermission(PermissionNames.Pages_Frame_Roles,L("Roles")); roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Create,L("CreateRole")); roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Update,L("UpdateRole")); roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Delete,L("DeleteRole")); #endregion } }
在設定完畢後,需要將該類整合到Core層/XXXCoreModule當前模組中,才能使得該部分許可權設定生效。
//配置許可權管理 Configuration.Authorization.Providers.Add<SurroundAuthorizationProvider>();
作為業務的入口,選單是較為直觀的體現方式,現在可以,為選單分配許可權了,擁有許可權的人才能看的到選單,同時後臺方法中也要有許可權判定,選單僅作為前端入口上的控制,許可權判定作為後端的控制。在MVC層的Startup/XXXNavigationProvider.cs中完成選單的配置工作,可以配置多級選單,每個選單可以配置相應的許可權,在生成選單判定時,如果父級選單許可權不足,則直接會跳過子級選單的判定。
new MenuItemDefinition(//基礎支撐 PageNames.FrameManage,L(PageNames.FrameManage),icon: "",requiredPermissionName: PermissionNames.Pages_Frame ).AddItem( new MenuItemDefinition(//組織機構 PageNames.OrganizationUnits,L(PageNames.OrganizationUnits),url: "/OrganizationUnits",icon: "",requiredPermissionName: PermissionNames.Pages_Frame_OrganizationUnits ) ).AddItem( new MenuItemDefinition(//使用者管理 PageNames.Users,L(PageNames.Users),url: "/Users",requiredPermissionName: PermissionNames.Pages_Framewww.cppcns.com_Users ) ).AddItem( new MenuItemDefinition(//角色管理 PageNames.Roles,L(PageNames.Roles),url: "/Roles",requiredPermissionName: PermissionNames.Pages_Frame_Roles ) ).AddItem( new MenuItemDefinition(//系統設定 PageNames.HostSettings,L(PageNames.HostSettings),url: "/HostSettings",requiredPermissionName: PermissionNames.Pages_Frame_HostSettihttp://www.cppcns.comngs ) )
在前端頁面上,對於按鈕級別的控制也通過許可權判定,Abp提供了判定方法,利用Razor語法進行按鈕控制
@if (await PermissionChecker.IsGrantedAsync(PermissionNames.Pages_Core_DataDictionary_Create)) { <button class="layui-btn layuiadmin-btn-dataDictionary" data-type="addDataDictionary">新增型別</button> }
在後端方法上,通常我喜歡直接在應用服務中的方法上做許可權判定(當然也可以前移到MVC層,但是這樣一來,針對於WebApi形式的Host層,又得多加一次判定了),利用AbpAuthorize特性,判定該方法需要哪幾個許可權才能訪問,而在mvc的控制器http://www.cppcns.com上做訪問認證。
[AbpAuthorize(PermissionNames.Pages_Core_DataDictionary_Create)] private async Task CreateDataDictionaryAsync(CreateOrUpdateDataDictionaryInput input) { }
2、角色與許可權
在Abp中,角色資訊儲存在abprole表中,角色與許可權間的關聯儲存在abppermission這張表中,一個角色有多個許可權,如果某個角色的許可權被去掉了,這張表中的相關記錄將由abp負責刪除,我們只需要完成掌控哪些許可權是這個角色有的就行。Abp中已經完成了角色的所有操作,但是前端部分採用的是bootstrap弄的,將其改造一波,成為layui風格。
在建立角色中,主要是將選中的許可權掛鉤到具體的某個角色上,該部分程式碼沿用abp中自帶的角色許可權處理方法。
private async Task CreateRole(CreateOrUpdateRoleInput input) { var role = ObjectMapper.Map<Role>(input.Role); role.SetNormalizedName(); CheckErrors(await _roleManager.CreateAsync(role)); var grantedPermissions = PermissionManager .GetAllPermissions() .Where(p => input.PermissionNames.Contains(p.Name)) .ToList(); await _roleManager.SetGrantedPermissionsAsync(role,grantedPermissions); }
指定角色Id,租戶Id及之前宣告的許可權名稱,在abppermission中可檢視到具體角色許可權。
3、使用者與角色
一個使用者可以承擔多個角色,履行不同角色的義務,作為一個業務系統最基本的單元,abp中提供了這些概念並在Module Zero模組中已經完成了對使用者的一系列操作,使用者資訊儲存在AbpUsers表中,使用者直接關聯的角色儲存在AbpUserRoles表中,abp中MVC版本採用的是bootstrap風格,因此,用layui風格完成一次替換,並且,改動一些頁面佈局。
Abp版本中,由於是土耳其大佬所開發的習慣,針對於姓和名做了拆分,因此對於我們的使用要做一次處理,我這先簡單處理了一下,並且在業務系統中,郵箱時有時無,因此也需要進行考慮。
[AbpAuthorize(PermissionNames.Pages_Frame_Users_Create)] private async Task CreateUser(CreateOrUpdateUserInput input) { var user = ObjectMapper.Map<User>(input.User); user.TenantId = AbpSession.TenantId; user.IsEmailConfirmed = true; user.Name = "Name"; user.Surname = "Surname"; //user.EmailAddress = string.Empty; await UserManager.InitializeOptionsAsync(AbpSession.TenantId); foreach (var validator in _passwordValidators) { CheckErrors(await validator.ValidateAsync(UserManager,user,AppConsts.DefaultPassword)); } user.Password = _passwordHasher.HashPassword(user,AppConsts.DefaultPassword); await _userManager.InitializeOptionsAsync(AbpSession.TenantId); CheckErrors(await _userManager.CreateAsync(user,AppConsts.DefaultPassword)); if (input.AssignedRoleNames != null) { CheckErrors(await _userManager.SetRoles(user,input.AssignedRoleNames)); } if (input.OrganizationUnitIds != null) { await _userManager.SetOrganizationUnitsAsync(user,input.OrganizationUnitIds); } CurrentUnitOfWork.SaveChanges(); }
此處對使用者個人單獨的許可權沒有去做處理,依照Abp的文件有那麼一句話,大多數應用程式中,基於角色的已經足夠使用了,如果想宣告特定許可權給使用者,那麼針對於使用者本身的角色許可權則被覆蓋。
至此,修改整合使用者、角色和許可權加入到系統中初步完成了,至於一些更為豐富的功能,待逐步加入中,車子再好,司機也得睡覺。
倉庫地址:https://gitee.com/530521314/Partner.Surround.git
到此這篇關於基於ABP框架實現RBAC(角色訪問控制)的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援我們。