1、前言
但凡業(yè)務(wù)系統(tǒng),授權(quán)是繞不開的一環(huán)。見過太多只在前端做菜單及按鈕顯隱控制,但后端裸奔的,覺著前端看不到,系統(tǒng)就安全,掩耳盜鈴也好,自欺欺人也罷,這里不做評論。在.NET
CORE中,也見過不少用操作過濾器來實現(xiàn)業(yè)務(wù)用例權(quán)限控制的,至少算是對后端做了權(quán)限控制。
但我們知道,操作過濾器,已經(jīng)算是過濾器管道中最靠后的,基本上緊挨著我們控制器方法執(zhí)行那里了,本身,操作過濾器也不是做權(quán)限控制的地方,雖然本身它能達(dá)到權(quán)限控制效果。為什么這么說,試想下,在過濾器管道之前,還有中間件處理管道,即便是過濾器管道執(zhí)行環(huán)節(jié),操作過濾器也是最靠后的,它往前還有授權(quán)過濾器,資源過濾器等,假如我在資源過濾器中緩存了請求結(jié)果,那權(quán)限控制基本上就廢了。
說這么多,只想表達(dá)一點(diǎn),合適的地方,合適的東西,干合適的事兒。在.NET
CORE中,官方推薦用策略去實現(xiàn)授權(quán)。策略授權(quán),是在授權(quán)中間件環(huán)節(jié)執(zhí)行,當(dāng)然能解決上述執(zhí)行流程先后順序的問題。但如果要直接應(yīng)用于我們業(yè)務(wù)系統(tǒng)中的權(quán)限控制,恐怕遠(yuǎn)遠(yuǎn)不夠,因為你不可能為每個api用例創(chuàng)建一個角色或策略,更主要的,權(quán)限控制還要動態(tài)授予或回收的,不做擴(kuò)展直接照搬,你是很難搞的。接下來,我們就來看看,如何基于core的授權(quán)機(jī)制,去實現(xiàn)我們傳統(tǒng)的用戶,角色,權(quán)限,及權(quán)限的動態(tài)授予與回收控制。
2、實現(xiàn)
我們先看看,菜單表概覽:
?
?
查詢中IsMenu代表是側(cè)邊欄菜單還是功能按鈕,這里我把按鈕級別的給篩選出來了,每個按鈕菜單都代表一個業(yè)務(wù)用例,也對應(yīng)我們一個控制器方法。
Code是唯一的,待會兒權(quán)限控制標(biāo)識,會采用這個字段。當(dāng)然你用主鍵ID也可以,但比較難記。實際運(yùn)維中,會把這些菜單按照業(yè)務(wù)分配給指定角色,再把指定角色分配給系統(tǒng)用戶。
接下來,定義一個Requirement,以權(quán)限碼作為主校驗對象:
public class PermissionRequirement : IAuthorizationRequirement { public
PermissionRequirement(params string[] codes) { this.Codes = codes; } public
string[] Codes { get; private set; } }
有Requirement,自然有RequirementHandler:
1 public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
2 { 3 private readonly IMenuService _menuService; 4 5 public
PermissionHandler(IMenuService menuService) 6 { 7 _menuService = menuService;
8 } 9 10 protected override async Task HandleRequirementAsync( 11
AuthorizationHandlerContext context,12 PermissionRequirement requirement) 13 {
14 var roles = context.User.Claims.FirstOrDefault(x => x.Type ==
ClaimTypes.Role).Value;15 if (!string.IsNullOrWhiteSpace(roles)) 16 { 17 var
roleIds = roles.Split(',', StringSplitOptions.RemoveEmptyEntries) 18 .Select(x
=>long.Parse(x)); 19 if (roleIds.Contains(1)) 20 { 21
context.Succeed(requirement);22 return; 23 } 24 25 var menus = await
_menuService.GetMenusByRoleIds(roleIds.ToArray());26 if (menus.Any()) 27 { 28
var codes = menus.Select(x => x.Code.ToUpper()); 29 if (requirement.Codes.Any(x
=> codes.Contains(x.ToUpper()))) 30 { 31 context.Succeed(requirement); 32 }
33 } 34 } 35 } 36 }
邏輯比較常規(guī),根據(jù)當(dāng)前用戶角色,獲取其所有菜單權(quán)限,然后與Requirement中聲明要求的菜單權(quán)限做對比,如果含有,則放行。到這兒,大家應(yīng)該都能看懂,典型的.NET
CORE權(quán)限控制組件。
接下來,定義一個授權(quán)過濾器特性:
1 public class PermissionFilter : Attribute, IAsyncAuthorizationFilter 2 {
3 private readonly IAuthorizationService _authorizationService; 4 private
readonly PermissionRequirement _requirement; 5 6 public
PermissionFilter(IAuthorizationService authorizationService,
PermissionRequirement requirement) 7 { 8 _authorizationService =
authorizationService; 9 _requirement = requirement; 10 } 11 12 public async
Task OnAuthorizationAsync(AuthorizationFilterContext context)13 { 14 var
result =await _authorizationService.AuthorizeAsync(context.HttpContext.User,
null, _requirement); 15 16 if (!result.Succeeded) 17 { 18 context.Result = new
JsonResult("您沒有此操作權(quán)限") 19 { 20 StatusCode = (int)HttpStatusCode.Forbidden 21
};22 } 23 } 24 }
從這兒開始,我估計有人就要看不懂了,下邊解釋下。首先,PermissionFilter是個授權(quán)過濾器,它實現(xiàn)了IAsyncAuthorizationFilter,所以會作為過濾器管道第一道去執(zhí)行。構(gòu)造函數(shù)中有2個參數(shù),IAuthorizationService直接注入,PermissionRequirement則通過特性標(biāo)記外部設(shè)置。在OnAuthorizationAsync重寫方法中,調(diào)用IAuthorizationService.AuthorizeAsync方法去做具體授權(quán)校驗工作,經(jīng)此橋接,授權(quán)流程就轉(zhuǎn)到我們最開始定義的PermissionHandler去了。本身core源碼中,IAuthorizationService是在授權(quán)中間件中使用到的,這里我借用了。注意,一旦過濾器注入了服務(wù),那此過濾器便不再能夠直接以打標(biāo)記的形式貼在控制器或方法上了,那種形式必須所有參數(shù)都直接指定。core中給出的方案,是TypeFilter,涉及到服務(wù)注入的時候。
那接下來,就是另一個重要對象了:
/// <summary> /// 權(quán)限特性 /// </summary> public class PermissionAttribute :
TypeFilterAttribute {public PermissionAttribute(params string[] codes) : base(
typeof(PermissionFilter)) { Arguments = new[] { new
PermissionRequirement(codes) }; } }
至此,各組件定義完畢,那我們使用就類似下邊這樣:
3、效果演示
guokun用戶角色:
?
?
?
?
?網(wǎng)站管理員角色對應(yīng)權(quán)限:
?
? 可以看到,是沒有勾選刪除部門的,那我們用這個賬戶來刪除下部門:
?
? 狀態(tài)碼403,并提示無權(quán)操作,刪除動作已經(jīng)被攔截了。那我們把刪除權(quán)限賦予網(wǎng)站管理員:
?
? 接下來再來刪除:
?
? 可以看到,已經(jīng)刪除部門成功。
3、總結(jié)
以上便是本項目權(quán)限控制的實現(xiàn)。認(rèn)證&授權(quán)這塊兒,如果要做好,還是得把core的整套機(jī)制弄清楚,最好能把源碼過一遍,不然根本搞不清楚需要怎么擴(kuò)展,每個擴(kuò)展點(diǎn)在什么時機(jī)觸發(fā)及生效。
熱門工具 換一換