1. 程式人生 > >Core + Vue 後臺管理基礎框架3——後端授權

Core + Vue 後臺管理基礎框架3——後端授權

1、前言

  但凡業務系統,授權是繞不開的一環。見過太多隻在前端做選單及按鈕顯隱控制,但後端裸奔的,覺著前端看不到,系統就安全,掩耳盜鈴也好,自欺欺人也罷,這裡不做評論。在.NET CORE中,也見過不少用操作過濾器來實現業務用例許可權控制的,至少算是對後端做了許可權控制。

  但我們知道,操作過濾器,已經算是過濾器管道中最靠後的,基本上緊挨著我們控制器方法執行那裡了,本身,操作過濾器也不是做許可權控制的地方,雖然本身它能達到許可權控制效果。為什麼這麼說,試想下,在過濾器管道之前,還有中介軟體處理管道,即便是過濾器管道執行環節,操作過濾器也是最靠後的,它往前還有授權過濾器,資源過濾器等,假如我在資源過濾器中快取了請求結果,那許可權控制基本上就廢了。

  說這麼多,只想表達一點,合適的地方,合適的東西,幹合適的事兒。在.NET CORE中,官方推薦用策略去實現授權。策略授權,是在授權中介軟體環節執行,當然能解決上述執行流程先後順序的問題。但如果要直接應用於我們業務系統中的許可權控制,恐怕遠遠不夠,因為你不可能為每個api用例建立一個角色或策略,更主要的,許可權控制還要動態授予或回收的,不做擴充套件直接照搬,你是很難搞的。接下來,我們就來看看,如何基於core的授權機制,去實現我們傳統的使用者,角色,許可權,及許可權的動態授予與回收控制。

2、實現

  我們先看看,選單表概覽:

 

 

  查詢中IsMenu代表是側邊欄選單還是功能按鈕,這裡我把按鈕級別的給篩選出來了,每個按鈕選單都代表一個業務用例,也對應我們一個控制器方法。 Code是唯一的,待會兒許可權控制標識,會採用這個欄位。當然你用主鍵ID也可以,但比較難記。實際運維中,會把這些選單按照業務分配給指定角色,再把指定角色分配給系統使用者。

  接下來,定義一個Requirement,以許可權碼作為主校驗物件:

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     }

  邏輯比較常規,根據當前使用者角色,獲取其所有選單許可權,然後與Requirement中宣告要求的選單許可權做對比,如果含有,則放行。到這兒,大家應該都能看懂,典型的.NET CORE許可權控制組件。

  接下來,定義一個授權過濾器特性:

 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("您沒有此操作許可權")
19                 {
20                     StatusCode = (int)HttpStatusCode.Forbidden
21                 };
22             }
23         }
24     }

  從這兒開始,我估計有人就要看不懂了,下邊解釋下。首先,PermissionFilter是個授權過濾器,它實現了IAsyncAuthorizationFilter,所以會作為過濾器管道第一道去執行。建構函式中有2個引數,IAuthorizationService直接注入,PermissionRequirement則通過特性標記外部設定。在OnAuthorizationAsync重寫方法中,呼叫IAuthorizationService.AuthorizeAsync方法去做具體授權校驗工作,經此橋接,授權流程就轉到我們最開始定義的PermissionHandler去了。本身core原始碼中,IAuthorizationService是在授權中介軟體中使用到的,這裡我借用了。注意,一旦過濾器注入了服務,那此過濾器便不再能夠直接以打標記的形式貼在控制器或方法上了,那種形式必須所有引數都直接指定。core中給出的方案,是TypeFilter,涉及到服務注入的時候。

  那接下來,就是另一個重要物件了:

/// <summary>
    /// 許可權特性
    /// </summary>
    public class PermissionAttribute : TypeFilterAttribute
    {
        public PermissionAttribute(params string[] codes)
            : base(typeof(PermissionFilter))
        {
            Arguments = new[] { new PermissionRequirement(codes) };
        }
    }

  至此,各元件定義完畢,那我們使用就類似下邊這樣:

3、效果演示

  guokun使用者角色:

 

 

 

 

 網站管理員角色對應許可權:

 

   可以看到,是沒有勾選刪除部門的,那我們用這個賬戶來刪除下部門:

 

   狀態碼403,並提示無權操作,刪除動作已經被攔截了。那我們把刪除許可權賦予網站管理員:

 

   接下來再來刪除:

 

   可以看到,已經刪除部門成功。

3、總結

  以上便是本專案許可權控制的實現。認證&授權這塊兒,如果要做好,還是得把core的整套機制弄清楚,最好能把原始碼過一遍,不然根本搞不清楚需要怎麼擴充套件,每個擴充套件點在什麼時機觸發及生