[ASP.NET MVC] 使用 AOP 驗證系統功能執行權限
在規劃設計一個系統功能時,除了完成商業邏輯業務需求之外,在系統設計階段,就要同時規劃好系統權限管理方式。
權限管理是檢核每一個功能執行時,是否擁有需要的權限,符合權限的人可以執行,而不符合權限的人就要阻檔下來,以免造成系統安全性問題,而檢查權限是一個通用的邏輯,建議要抽離至獨立功能。
以權限管理來舉例,在開發一個功能時,在方法內的程式碼只要專注處理業務邏輯就好,而關於執行方法的權限檢查,建議要跟業務邏輯區分開來,這樣的做法叫做 AOP (Aspect-Oriented Programming),也稱為關注點分離的開發模式,就是將主要邏輯與通用邏輯分開來寫,各別維護,當需要的時候附加宣告就可以了。
而今天要實作的是在 ASP.NET MVC 設計自己的權限管理,會使用到 AOP (Aspect-Oriented Programming) 概念,先設計角色清單,然後在執行功能前,驗證角色是否符合權限驗證。
Contents
什麼是 AOP ?
AOP (Aspect-Oriented Programming) 維基百科中文翻譯是「剖面導向程式設計」,其概念是將橫切關注點與業務主題進行分離,提高程式模組化架構。
常見實作的例子有 Log 記錄、權限檢查、效能監測等等,其概念就是將業務邏輯與通用邏輯區分開來執行。
一個系統內有多數的功能有相同的需求,如果在每個功能內都寫相同的程式碼,就會過於重複了,所以將通用邏輯抽離出來,有需要時再附加即可,讓各方法內專注自身的工作。
了解 AOP 的概念,是幫助我們設計具有關注點分離的系統架構,分享一篇我覺得介紹 AOP 不錯的文章: 連結。
ASP.NET MVC 實作 AOP 概念
在 ASP.NET MVC 要實作 AOP 概念,就會使用到「篩選器 (Filter)」,這是一個擴充操作屬性,讓原有方法附加更多的應用。
ASP.NET MVC 提供的篩選器 (Filter) 可實作以下4 種應用:
- IAuthorizationFilter:授權篩選器用於實現控制器操作的身份驗證和授權。例如在執行功能前的身份檢查,也就是本文的實作範例。
- IActionFilter:操作篩選器包含在控制器操作執行之前和之後執行的邏輯。例如可以使用操作篩選器修改控制器操作返回的檢視數據。
- IResultFilter:結果篩選器包含在執行檢視結果之前和之後執行的邏輯。 例如您可能希望在檢視呈現給瀏覽器之前修改視圖結果。
- IExceptionFilter:異常篩選器是要運行的最後一種篩選器類型。可以使用異常篩選器來處理控制器操作或控制器操作結果引發的錯誤。您還可以使用異常篩選器來記錄錯誤。
每種篩選器 (Filter) 都有自己的生命週期事件,通常都是覆寫生命週期事件來執行附加功能。
AuthorizeAttribute 是繼承 IAuthorizationFilter 的類別,裡面有以下的生命週期事件:
而我這次的授權篩選器會覆寫 2 個方法:
protected virtual bool AuthorizeCore(HttpContextBase httpContext)
:用在身份權限檢查,檢查成功回傳 true,失敗則回傳 false。
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
:當權限檢查失敗時的導頁語法。
透過覆寫方法,就可以在適合的生命週期事件完成權限檢查。
而另一個常用的操作篩選器 ActionFilterAttribute 繼承於 IActionFilter,有以下 4 個方法可覆寫:
這類型篩選器很適合用在 Log 記錄或效能監測等功能。
ASP.NET MVC 官方有一篇介紹文章可以了解此概念:連結。
接下來我們就開始實作 ASP.NET MVC 的權限驗證身份範例。
系統角色定義
在實作權限管理之前,要先定義系統可執行的角色有哪些,每一個使用者都被賦與一個角色。
而角色與角色之間可以規劃高低之分,例如擁有管理者的權限就包含一般使用者的權限。
而我這次的教學範例就規劃 3 個角色:
0: 未登入訪客
1: 一般會員
2: 系統管理員
未登入的人瀏覽網站都是未登入訪客身份,在登入過後由資料庫內取得登入者角色,登入後身份為一般會員或是系統管理員,而系統管理員為最大權限角色。
建立專案
開啟 Visual Studio 2022,建立新專案為「ASP.NET Web 應用程式 (.NET Framework)」。
輸入專案名稱、位置之後,選擇「MVC」範本,就可以建立專案。
新增 AuthorizeFilter 權限篩選類別
我是先新增一個資料夾為 Filters,在裡面新增 AuthorizeFilter 類別。
讓 AuthorizeFilter 類別繼承 AuthorizeAttribute
在 AuthorizeFilter.cs 裡面先加入角色定義,使用 enum 可以限定值的範圍,不會因打錯字而出錯。
1 2 3 4 5 6 7 |
/// <summary> /// 角色定義 /// </summary> public enum UserRole { Visitor = 0, Member = 1, Admin = 2 } |
在 AuthorizeFilter 加入屬性與建構子。
1 2 3 4 5 6 7 8 9 10 |
public UserRole CheckUserRole { get; set; } //要檢查的角色權限 /// <summary> /// 建構子 /// </summary> /// <param name="check"></param> public AuthorizeFilter(UserRole checkRole) { CheckUserRole = checkRole; } |
此篩選器實作時都需要傳入要檢查的身份權限。
覆寫生命週期事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
/// <summary> /// 自訂權限檢查 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> /// <exception cref="ArgumentNullException"></exception> protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("httpContext"); } bool CheckResult = false; //false 表示不符合權限 // 取得用戶角色 UserRole userRole = UserRole.Visitor; //預設角色 if (httpContext.Session["UserRole"] != null) { // 從 Session 取得角色 userRole = (UserRole)Enum.Parse(typeof(UserRole), httpContext.Session["UserRole"].ToString(), true); } // 如果使用者是管理員角色直接通過 if (userRole == UserRole.Admin) { return true; } // 檢查一般會員權限 if (CheckUserRole == UserRole.Member) { if (userRole == UserRole.Member) { CheckResult = true; } } return CheckResult; } /// <summary> /// 權限檢查失敗時動作 /// </summary> /// <param name="filterContext"></param> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // 當權限檢查失敗時,跳頁至登入頁 filterContext.Result = new RedirectResult("~/Home/Login"); } |
會員的身份設定都來至 Session["UserRole"]
裡面的設定值,所以在登入的時候,需要在 Session["UserRole"]
裡面寫入身份值,例如 “Member” 或是 “Admin”。
語法 HandleUnauthorizedRequest
是覆寫驗證失敗時的動作,如果沒有覆寫此方法,那會回傳 HTTP Error 401.0 – Unauthorized 的錯誤訊息。
而我的範例是驗證失敗時,直接導向至 “~/Home/Login” 登入網址,這裡可以設定自定的登入頁面網址。
到這裡我們的權限篩選器就完成了,接下來就示範如何呼叫。
執行權限檢查
接著我們在預設的 \Controllers\HomeController.cs 新增幾個 Action 來示範使用方式。
如果 Action 是任何人都可以訪問的,那就不需要附加任何的驗證。
會員權限檢查
那假設某一 Action 是限定已登入的會員才可以訪問,可以在 Action 上面增加屬性:[AuthorizeFilter(UserRole.Member)]
如果是管理者權限的檢查,設定方法一樣,只是檢查的權限不同而已。
如果未登入或是權限不夠,會被導向至登入頁。
登入後權限設定
要設定登入角色的方式就是直接寫值到 Session
內,例如從資料庫驗證帳號密碼成功後,從資料庫的欄位取出角色的身分,然後寫入 Session
內。
Session["UserRole"] = "Member";
當 Session["UserRole"]
內有身份設定後,就可以正常訪問 MemberPage 的 Action 了。
適用整個 Controller 驗證
剛剛教學的是在 Action 附加身份檢查,如果是某一個 Controller 所有的 Action 都要身份檢查,那就不需要在每一個 Action 上面附加身份檢查,可以直接在 Controller 設定,就會讓所有 Action 都生效。
例如我新增一個 MemberController 類別,在 Controller 附加身份檢查屬性:[AuthorizeFilter(UserRole.Member)]
就可以讓底下 Action 全部都生效。
以上範例是簡單示範系統權限檢查的方式,透過 AOP 的關注點分離開發模式,可以模組化我們的系統架構,這也是好的物件導向設計觀念。
範例下載
相關學習文章
- [ASP.NET MVC] 多國語系切換 – 使用資料庫管理與兼容 JavaScript 顯示(附範例)
- [ASP.NET MVC] 系統 Log 設計思維與客製化 Log 系統 (附範例)
- [ASP.NET MVC] 生命週期介紹
如果你在學習上有不懂的地方,需要諮詢服務,可以參考站長服務,我想辨法解決你的問題
如果文章內容有過時、不適用或錯誤的地方,幫我在下方留言通知我一下,謝謝