基于ABP框架實現(xiàn)RBAC(角色訪問控制)
在業(yè)務(wù)系統(tǒng)需求規(guī)劃過程中,通常對于諸如組織機構(gòu)、用戶和角色等這種基礎(chǔ)功能,通常是將這部分功能規(guī)劃到通用子域中,這也說明了,對于這部分功能來講,是系統(tǒng)的基石,整個業(yè)務(wù)體系是建立于這部分基石之上的,當(dāng)然,還有諸如多語言、設(shè)置管理、認(rèn)證和授權(quán)等。對于這部分功能,ABP中存在這些概念,并且通過Module Zero模塊完成了這些概念。
一、角色訪問控制之RBAC
RBAC:Role Based Access Control,基于角色的訪問控制,這在目前大多數(shù)軟件中來講已經(jīng)算得上是普遍應(yīng)用了,最常見的結(jié)構(gòu)如下,結(jié)構(gòu)簡單,設(shè)計思路清晰。

但是也存在其它升級版的設(shè)計,諸如用戶權(quán)限表、角色組、用戶組的概念等,具體分類有RBAC0、RBAC1、RBAC2等,后者功能越來越強大,也越來越復(fù)雜。
- RBAC0:是RBAC的核心思想。
- RBAC1:是把RBAC的角色分層模型。
- RBAC2:增加了RBAC的約束模型。
- RBAC3:整合RBAC2 + RBAC1。
二、ABP中的RBAC
在Abp中,已經(jīng)集成了這些概念,并在ModuleZero模塊中實現(xiàn)了這些概念,基于IdentityServer4的ModuleZero模塊完成了封裝。對于我們大多數(shù)以業(yè)務(wù)為中心的開發(fā)人員來講,不應(yīng)該又去造一個輪子,而是應(yīng)該開好這輛車。首先看下Abp中的RBAC模型

在這其中權(quán)限表中記錄了用戶與權(quán)限,角色與權(quán)限兩部分。對于權(quán)限通常指的是功能權(quán)限和數(shù)據(jù)權(quán)限兩部分,一般來講,大多指的是功能權(quán)限,這種通過角色與權(quán)限進行管理即可,如還有用戶部分的功能區(qū)分,則可以再使用上用戶與權(quán)限,而對于數(shù)據(jù)權(quán)限,可以利用用戶與權(quán)限部分,個人用的比較少,但是,可以想象到這么一個場景,針對于一家門店內(nèi)的多個店長,角色相同即相應(yīng)的權(quán)限相同,但各自關(guān)心的數(shù)據(jù)來源不同,關(guān)心東部、南部等數(shù)據(jù),而不關(guān)心西部、北部數(shù)據(jù),因此可以在數(shù)據(jù)層面進行劃分,比如設(shè)置數(shù)據(jù)來源,東南西北,對于數(shù)據(jù)來源進行權(quán)限關(guān)聯(lián),這樣一來用戶本身如果擁有東部數(shù)據(jù)權(quán)限,則只能看到東部數(shù)據(jù)。
1、權(quán)限聲明及應(yīng)用
在Abp中,需要首先在Core層/Authorization/PermissionNames.cs中聲明權(quán)限,Abp權(quán)限部分設(shè)計原則是:先聲明再使用。
/// <summary>
/// 權(quán)限命名
/// </summary>
public static class PermissionNames
{
#region 頂級權(quán)限
public const string Pages = "Pages";
#endregion
#region 基礎(chǔ)支撐平臺
public const string Pages_Frame = "Pages.Frame";
#region 租戶管理
public const string Pages_Frame_Tenants = "Pages.Frame.Tenants";
#endregion
#region 組織機構(gòu)
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中設(shè)置具體權(quán)限,在此處設(shè)置權(quán)限時,可以根據(jù)權(quán)限設(shè)計時候的職責(zé)劃分,比如如果僅僅是多租戶需要這部分,那便設(shè)置權(quán)限范圍為多租戶即可。
public class SurroundAuthorizationProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
#region 頂級權(quán)限
var pages = context.CreatePermission(PermissionNames.Pages, L("Pages"));
#endregion
#region 基礎(chǔ)支撐平臺
var frame = pages.CreateChildPermission(PermissionNames.Pages_Frame, L("Frame"));
#region 租戶管理
frame.CreateChildPermission(PermissionNames.Pages_Frame_Tenants, L("Tenants"), multiTenancySides: MultiTenancySides.Host);
#endregion
#region 組織機構(gòu)
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
}
}
在設(shè)置完畢后,需要將該類集成到Core層/XXXCoreModule當(dāng)前模塊中,才能使得該部分權(quán)限設(shè)置生效。
//配置權(quán)限管理 Configuration.Authorization.Providers.Add<SurroundAuthorizationProvider>();
作為業(yè)務(wù)的入口,菜單是較為直觀的體現(xiàn)方式,現(xiàn)在可以,為菜單分配權(quán)限了,擁有權(quán)限的人才能看的到菜單,同時后臺方法中也要有權(quán)限判定,菜單僅作為前端入口上的控制,權(quán)限判定作為后端的控制。在MVC層的Startup/XXXNavigationProvider.cs中完成菜單的配置工作,可以配置多級菜單,每個菜單可以配置相應(yīng)的權(quán)限,在生成菜單判定時,如果父級菜單權(quán)限不足,則直接會跳過子級菜單的判定。
new MenuItemDefinition(//基礎(chǔ)支撐
PageNames.FrameManage,
L(PageNames.FrameManage),
icon: "",
requiredPermissionName: PermissionNames.Pages_Frame
).AddItem(
new MenuItemDefinition(//組織機構(gòu)
PageNames.OrganizationUnits,
L(PageNames.OrganizationUnits),
url: "/OrganizationUnits",
icon: "",
requiredPermissionName: PermissionNames.Pages_Frame_OrganizationUnits
)
).AddItem(
new MenuItemDefinition(//用戶管理
PageNames.Users,
L(PageNames.Users),
url: "/Users",
icon: "",
requiredPermissionName: PermissionNames.Pages_Frame_Users
)
).AddItem(
new MenuItemDefinition(//角色管理
PageNames.Roles,
L(PageNames.Roles),
url: "/Roles",
icon: "",
requiredPermissionName: PermissionNames.Pages_Frame_Roles
)
).AddItem(
new MenuItemDefinition(//系統(tǒng)設(shè)置
PageNames.HostSettings,
L(PageNames.HostSettings),
url: "/HostSettings",
icon: "",
requiredPermissionName: PermissionNames.Pages_Frame_HostSettings
)
)
在前端頁面上,對于按鈕級別的控制也通過權(quán)限判定,Abp提供了判定方法,利用Razor語法進行按鈕控制
@if (await PermissionChecker.IsGrantedAsync(PermissionNames.Pages_Core_DataDictionary_Create))
{
<button class="layui-btn layuiadmin-btn-dataDictionary" data-type="addDataDictionary">添加類型</button>
}
在后端方法上,通常我喜歡直接在應(yīng)用服務(wù)中的方法上做權(quán)限判定(當(dāng)然也可以前移到MVC層,但是這樣一來,針對于WebApi形式的Host層,又得多加一次判定了),利用AbpAuthorize特性,判定該方法需要哪幾個權(quán)限才能訪問,而在mvc的控制器上做訪問認(rèn)證。
[AbpAuthorize(PermissionNames.Pages_Core_DataDictionary_Create)]
private async Task CreateDataDictionaryAsync(CreateOrUpdateDataDictionaryInput input)
{
}
2、角色與權(quán)限
在Abp中,角色信息存儲在abprole表中,角色與權(quán)限間的關(guān)聯(lián)存儲在abppermission這張表中,一個角色有多個權(quán)限,如果某個角色的權(quán)限被去掉了,這張表中的相關(guān)記錄將由abp負(fù)責(zé)刪除,我們只需要完成掌控哪些權(quán)限是這個角色有的就行。Abp中已經(jīng)完成了角色的所有操作,但是前端部分采用的是bootstrap弄的,將其改造一波,成為layui風(fēng)格。

在創(chuàng)建角色中,主要是將選中的權(quán)限掛鉤到具體的某個角色上,該部分代碼沿用abp中自帶的角色權(quán)限處理方法。
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及之前聲明的權(quán)限名稱,在abppermission中可查看到具體角色權(quán)限。

3、用戶與角色
一個用戶可以承擔(dān)多個角色,履行不同角色的義務(wù),作為一個業(yè)務(wù)系統(tǒng)最基本的單元,abp中提供了這些概念并在Module Zero模塊中已經(jīng)完成了對用戶的一系列操作,用戶信息存儲在AbpUsers表中,用戶直接關(guān)聯(lián)的角色保存在AbpUserRoles表中,abp中MVC版本采用的是bootstrap風(fēng)格,因此,用layui風(fēng)格完成一次替換,并且,改動一些頁面布局。

Abp版本中,由于是土耳其大佬所開發(fā)的習(xí)慣,針對于姓和名做了拆分,因此對于我們的使用要做一次處理,我這先簡單處理了一下,并且在業(yè)務(wù)系統(tǒng)中,郵箱時有時無,因此也需要進行考慮。
[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();
}
此處對用戶個人單獨的權(quán)限沒有去做處理,依照Abp的文檔有那么一句話,大多數(shù)應(yīng)用程序中,基于角色的已經(jīng)足夠使用了,如果想聲明特定權(quán)限給用戶,那么針對于用戶本身的角色權(quán)限則被覆蓋。

至此,修改整合用戶、角色和權(quán)限加入到系統(tǒng)中初步完成了,至于一些更為豐富的功能,待逐步加入中,車子再好,司機也得睡覺。
倉庫地址:https://gitee.com/530521314/Partner.Surround.git
到此這篇關(guān)于基于ABP框架實現(xiàn)RBAC(角色訪問控制)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.NET中利用js讓子窗體向父頁面?zhèn)髦档膶崿F(xiàn)方法
.NET中利用js讓子窗體向父頁面?zhèn)髦档膶崿F(xiàn)方法,需要的朋友可以參考一下2013-02-02
在ASP.net中保存/取出圖片入/從SQL數(shù)據(jù)庫
在ASP.net中保存/取出圖片入/從SQL數(shù)據(jù)庫...2006-09-09
.net decimal保留指定的小數(shù)位數(shù)(不四舍五入)
大家都知道decimal保留指定位數(shù)小數(shù)的時候,.NET自帶的方法都是四舍五入的。那么如何讓decimal保留指定位數(shù)小數(shù)的時候不四舍五入呢,下面通過這篇文中的示例代碼來一起看看吧。2016-12-12
Asp.net FCKEditor 2.6.3 上傳文件沒有權(quán)限解決方法
到Fckeditor官方網(wǎng)站下載FredCK.FCKeditorV2.vs2005 (asp.net)2009-02-02
.NET CORE中使用AutoMapper進行對象映射的方法
這篇文章主要給大家介紹了關(guān)于.NET CORE中使用AutoMapper進行對象映射的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用.NET CORE具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

