ABP+AdminLTE+Bootstrap Table權限管理系統一期
Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS
角色訪問控制(RBAC)
角色訪問控制(RBAC)應該是目前用得最多也是關注最多的權限管理模型了。
權限(Permission
)與角色(Role
)相關聯,用戶(User
)通過成為適當角色的成員而得到這些角色的權限。這就極大地簡化了權限的管理。
RBAC引入了角色(Role
)概念,目的應該是解耦了Permission
與User
之間的關系,直接授權給Role
,而不是直接授權給用戶,或者用戶組。
基於角色的訪問控制方法(RBAC)的顯著的兩大特征是:
- 由於角色/權限之間的變化比角色/用戶關系之間的變化相對要慢得多,減小了授權管理的復雜性,降低管理開銷。
- 靈活地支持企業的安全策略,並對企業的變化有很大的伸縮性。
RBAC支持三個著名的安全原則:最小權限原則,責任分離原則和數據抽象原則。
(1)最小權限原則之所以被RBAC所支持,是因為RBAC可以將其角色配置成其完成任務所需要的最小的權限集。
(2)責任分離原則可以通過調用相互獨立互斥的角色來共同完成敏感的任務而體現,比如要求一個計帳員和財務管理員共參與同一過帳。
(3)數據抽象可以通過權限的抽象來體現,如財務操作用借款、存款等抽象權限,而不用操作系統提供的典型的讀、寫、執行權限。然而這些原則必須通過RBAC各部件的詳細配置才能得以體現。
RBAC結構
RBAC認為權限授權實際上是Who、What、How的問題。在RBAC模型中,who、what、how構成了訪問權限三元組,也就是“Who對What(Which)進行How的操作”。
- Who:權限的擁用者或主體(如Principal、User、Group、Role、Actor等等)
- What:權限針對的對象或資源(Resource、Class)。
- How:具體的權限(Privilege,正向授權與負向授權)。
- Operator:操作。表明對What的How操作。也就是Privilege+Resource
- Role:角色,一定數量的權限的集合。權限分配的單位與載體,目的是隔離User與Privilege的邏輯關系.
- Group:用戶組,權限分配的單位與載體。權限不考慮分配給特定的用戶而給組。組可以包括組(以實現權限的繼承),也可以包含用戶,組內用戶繼承組的權限。User與Group是多對多的關系。Group可以層次化,以滿足不同層級權限控制的要求。
- RBAC的關注點在於Role和User, Permission的關系。稱為User
assignment(UA)
和Permission assignment(PA)
.關系的左右兩邊都是Many-to-Many關系。就是user可以有多個role,role可以包括多個user。 - 凡是用過RDBMS都知道,n:m 的關系需要一個中間表來保存兩個表的關系。這UA和PA就相當於中間表。事實上,整個RBAC都是基於關系模型。
- Session在RBAC中是比較隱晦的一個元素。標准上說:每個Session是一個映射,一個用戶到多個role的映射。當一個用戶激活他所有角色的一個子集的時候,建立一個session。每個Session和單個的user關聯,並且每個User可以關聯到一或多個Session.
- 在RBAC系統中,User實際上是在扮演角色(Role),可以用Actor來取代User,這個想法來自於
Business Modeling With UML
一書Actor-Role模式。考慮到多人可以有相同權限,RBAC引入了Group的概念。Group同樣也看作是Actor。而User的概念就具象到一個人。
這里的Group和GBAC(Group-Based Access Control)中的 - Group(組)不同。GBAC多用於操作系統中。其中的Group直接和權限相關聯,實際上RBAC也借鑒了一些GBAC的概念。
- Group和User都和組織機構有關,但不是組織機構。二者在概念上是不同的。組織機構是物理存在的公司結構的抽象模型,包括部門,人,職位等等,而權限模型是對抽象概念描述。組織結構一般用Martin fowler的Party或責任模式來建模。
- Party模式中的Person和User的關系,是每個Person可以對應到一個User,但可能不是所有的User都有對應的Person。Party中的部門Department或組織Organization,都可以對應到Group。反之Group未必對應一個實際的機構。例如,可以有副經理這個Group,這是多人有相同職責。
- 引入Group這個概念,除了用來解決多人相同角色問題外,還用以解決組織機構的另一種授權問題:例如,A部門的新聞我希望所有的A部門的人都能看。有了這樣一個A部門對應的Group,就可直接授權給這個Group。
ABP權限管理
首先ABP權限管理也是基於RBAC的。
在 RBAC之中,包含用戶users(USERS)、角色roles(ROLES)、目標objects(OBS)、操作operations(OPS)、許可權permissions(PRMS)五個基本數據元素,權限被賦予角色,而不是用戶,當一個角色被指定給一個用戶時,此用戶就擁有了該角色所包含的權限。會話sessions是用戶與激活的角色集合之間的映射。
然后我們來看一下一開始就生成的AbpPermissions
表。
我們可以看到一個權限(AbpPermissions
)有以下屬性:
Name:系統范圍內的唯一名字。把它定義為一個字符串常量是個不錯的注意。我們傾向於將“.”分割不同的層級,但並不要求這么做。你可以設置你任何喜歡的名字。唯一的規則就是這個名字必須是唯一的。
- Display Name:使用一個本地化的字符串去顯示權限到UI。
- Description:和Display Name類似。
- IsGrantedByDefault:此權限是否授權給(已登陸)所有用戶,除非顯示指定。通常設置為False(默認值)。
- MultiTenancySides:對租戶應用程序,一個權限可以基於租戶或者主機(原文:host)。這是個枚舉標識,因此權限可以應用於不同方面。一個權限可以有父權限和子權限。
定義權限
前面幾篇文章,我們已經完成了用戶詳情列表的增刪改查。
這里我們來給他們加上權限,首先,在module-zero
項目中已經完整實現了。我們先看下默認已經存在的代碼。
public class ABPCMSAuthorizationProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
context.CreatePermission(PermissionNames.Pages_Users, L("Users"));
context.CreatePermission(PermissionNames.Pages_Roles, L("Roles"));
context.CreatePermission(PermissionNames.Pages_Tenants, L("Tenants"), multiTenancySides: MultiTenancySides.Host);
}
private static ILocalizableString L(string name)
{
return new LocalizableString(name, ABPCMSConsts.LocalizationSourceName);
}
}
我們仿照這段代碼,新建一個類UserInfoAuthorizationProvider
,繼承自AuthorizationProvider
,看下代碼:
public class UserInfoAuthorizationProvider: AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
var pages = context.GetPermissionOrNull(PermissionNames.Pages);
if (pages == null)
pages = context.CreatePermission(PermissionNames.Pages, L("Pages"));
var UserInfos= pages.CreateChildPermission(PermissionNames.Pages_UserInfos, L("UserInfos"));
UserInfos.CreateChildPermission(PermissionNames.Pages_UserInfos_Create, L("UserInfosCreate"));
UserInfos.CreateChildPermission(PermissionNames.Pages_UserInfos_Delete, L("UserInfosDelete"));
UserInfos.CreateChildPermission(PermissionNames.Pages_UserInfos_Update, L("UserInfosUpdate"));
}
private static ILocalizableString L(string name)
{
return new LocalizableString(name, ABPCMSConsts.LocalizationSourceName);
}
}
然后再PermissionNames
中依樣添加如下常量。
public static class PermissionNames
{
public const string Pages_Tenants = "Pages.Tenants";
public const string Pages_Users = "Pages.Users";
public const string Pages_Roles = "Pages.Roles";
public const string Pages = "Pages";
public const string Pages_UserInfos = "Pages.UserInfos";
public const string Pages_UserInfos_Create = "Pages.UserInfo.Create";
public const string Pages_UserInfos_Delete = "Pages.UserInfo.Delete";
public const string Pages_UserInfos_Update = "Pages.UserInfo.Update";
}
注冊TaskAuthorizationProvider
定位到ABPCMSCoreModule.cs
,這里已經有關於 Configuration.Authorization.Providers.Add<ABPCMSAuthorizationProvider>();
我們在他的下面補上我們自己的類。
Configuration.Authorization.Providers.Add<UserInfoAuthorizationProvider>();
給admin賦值
定位到HostRoleAndUserCreator
和TenantRoleAndUserBuilder
類
然后手動在下面添加上如下代碼。
//Grant all tenant permissions
var permissions = PermissionFinder
.GetAllPermissions(new ABPCMSAuthorizationProvider())
.Where(p => p.MultiTenancySides.HasFlag(MultiTenancySides.Host))
.ToList();
在這段代碼之下,添加如下代碼。
//將UserInfoAuthorizationProvider相關Permission賦予給Admin
var UserInfoAuthorization =
PermissionFinder.GetAllPermissions(new UserInfoAuthorizationProvider()).ToList();
permissions.AddRange(UserInfoAuthorization);
刪除數據庫執行命令並生成數據庫
有了上面的基礎,我們先刪除數據庫,在生成數據庫,有人說這里為什么要刪除數據庫,因為在在ABP模板項目中暫未提供用戶角色權限管理功能,但在AbpZero中提供了該功能,支持按用戶或角色賦予權限,在數據庫初始化的時候,將權限賦給Admin。我們已經創建了數據庫,所以要刪除數據庫重新初始化。
執行update-database -Verbose命令,於是生成數據庫如下。
有了數據,admin用戶已經賦予了權限,自然不需要我們去管,但是如果我們切換用戶進來,就沒有權限,我們怎么出給他加上權限呢。
我們直接在控制方法上添加上標簽[AbpAuthorize(PermissionNames.Pages_UserInfos)]
就可以了
[AbpAuthorize(PermissionNames.Pages_UserInfos)]
[HttpGet]
[DontWrapResult]
public async Task<ActionResult> GetUserInfo()
{
string pageNumber = string.IsNullOrWhiteSpace(Request["pageNumber"]) ? "0" : Request["pageNumber"];
string pageSize = string.IsNullOrWhiteSpace(Request["pageSize"]) ? "20" : Request["pageSize"];
var users = (await _userAppService.GetAll(new PagedResultRequestDto { MaxResultCount = int.MaxValue })).Items;
var Userlist = users.Skip(int.Parse(pageNumber) * int.Parse(pageSize)).Take(int.Parse(pageSize)).ToList();
int totaldata = Userlist.Count();
var result = new { total = 10, rows = Userlist };
return Json(result, JsonRequestBehavior.AllowGet);
}
同時這里是用區別的。
- 在應用服務層中我們直接直接使用
[AbpAuthorize]
特性, - 但在MVC控制器中使用
[AbpMvcAuthorize]
特性, - Web API控制器中使用
[AbpApiAuthorize]
。
AbpAuthorize
屬性說明(AbpAuthorize attribute notes)
Abp使用動態方法攔截進行權限驗證。因此,使用AbpAuthorize
特性的方法會有些限制。如下:
- 不能應用於私有(private)方法
- 不能應用於靜態(static)方法
- 不能應用於非注入(non-injected)類(我們必須用依賴注入)。
此外, AbpAuthorize
特性可以應用於任何的Public方法,如果此方法被接口調用(比如在Application Services
中通過接口調用)- 方法是虛(virtual)方法,如果此方法直接被類引用進行調用(像是ASP.NET MVC 或 Web API 的控制器)。
- 方式是虛(
virtual
)方法,如果此方法是protected
。
注意:有三種AbpAuthorize
特性:
在應用程序服務中(application layer
),我們使用Abp.Authorization.AbpAuthorize
; - 在MVC控制器(web layer)中,我們使用
Abp.Web.Mvc.Authorization.AbpMvcAuthorize
; - 在ASP.NET Web API,我們使用
Abp.WebApi.Authorization.AbpApiAuthorize
。
這三個類繼承自不同的地方。 - 在MVC中,它繼承自MVC自己的Abp.Web.Mvc.Authorization.AbpMvcAuthorize類。
- 在Web API,它繼承自Web API 的Abp.WebApi.Authorization.AbpApiAuthorize類。因此,它最好是繼承到MVC和Web API中。
- 但是,在Application 層,它完全是由Abp自己實現沒有擴展子任何類。
IPermissionChecker及前端授權
AbpAuthorize
適用於大部分的情況,但是某些情況下,我們還是需要自己在方法體里進行權限驗證。我們可以注入和使用IPermissionChecker
對象。如下邊的代碼所示:
public void CreateUser(CreateOrUpdateUserInput input)
{
if (!PermissionChecker.IsGranted("Administration.UserManagement.CreateUser"))
{
throw new AbpAuthorizationException("You are not authorized to create user!");
}
//A user can not reach this point if he is not granted for "Administration.UserManagement.CreateUser" permission.
}
因為授權一般在應用服務層中進行,所以ABP默認在ApplicationService
基類注入並定義了PermissionChecker
屬性。這樣,在應用服務層就可以直接使用PermissionChecker
屬性進行權限檢查。如下面的代碼:
protected override async Task<User> GetEntityByIdAsync(long id)
{
bool UserInfos = PermissionChecker.IsGranted(PermissionNames.Pages_UserInfos);
//如果當前人員沒有權限,則拋出異常
if (!UserInfos)
{
throw new AbpAuthorizationException("沒有權限!");
}
var user = Repository.GetAllIncluding(x => x.Roles).FirstOrDefault(x => x.Id == id);
return await Task.FromResult(user);
}
- 前台html ,javascript頁面中加入以前代碼即可過濾權限:
@if (IsGranted(PermissionNames.Pages_UserInfos))
{
//業務代碼
}
- 我們可以使用定義在abp.auth命名空間下的API.
abp.auth.hasPermission('PermissionNames.Pages_UserInfos);
你也可以使用abp.auth.grantedPermissions來獲得所有授權的權限或者使用 abp.auth.allPermissions來獲取所有應用中可用的權限名。
注意:自ABP 0.7.8版本開始,將javascript端的abp.auth.hasPermission更名為abp.auth.isGranted。hasPermission已經過時了。在新的項目中不要使用abp.auth.hasPermission。
總結
其實ABP module-zero中的權限管理讓人痛苦,如果我要去對AbpPermissions
表等其他表進行增刪查改的時候,很痛苦,或者給要admin賦值的時候不可能每次都刪數據庫去實現,雖然在abp zero已經實現。我覺得最好的方式是不要module-zero
那套東西,一切都自己去創建一套RBAC。
送上本文源碼:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS。
最后送上福利。
github地址: https://github.com/Jimmey-Jiang/WY.MVC.RMS
一套完整權限管理系統,codefirst,直接生成數據庫可用。