企業後台系統最容易踩雷的地方之一,不是功能複雜度,而是權限設計。 早期用「if 判斷角色」硬寫的權限邏輯,往往在系統成長到一定規模後變成維護噩夢。 這篇說明如何用 RBAC(Role-Based Access Control)模型搭配動態選單, 做出可維護、可擴充的權限架構。

為什麼不能直接用 if 判斷角色

最常見的反面案例是這樣寫的:

if (user.Role == "Admin" || user.Role == "Manager")
{
    // 允許存取
}

這種寫法在角色只有兩三種時看起來沒問題,但當需求變成「新增一個『財務主管』角色, 可以看報表但不能改設定」,你就得回頭改每一個散落在程式碼各處的 if 判斷。 這正是 RBAC 要解決的問題:把「誰能做什麼」從程式碼邏輯,搬到可以動態設定的資料層。

RBAC 的核心資料模型

一個基本的 RBAC 架構通常需要四張核心表:

  • Users(使用者):系統帳號本身
  • Roles(角色):如「系統管理員」「一般員工」「財務主管」
  • Permissions(權限):如「查看報表」「編輯訂單」「刪除使用者」
  • UserRoles / RolePermissions(關聯表):使用者對應角色、角色對應權限,都是多對多關係

這樣設計的好處是:新增一個角色、調整一個角色能做什麼,全部都是資料異動, 完全不用改程式碼、不用重新部署。

在 ASP.NET Core 裡實作 Middleware 攔截

與其在每個 Controller Action 裡重複寫權限檢查,更好的做法是用自訂的 Authorization Policy 或 Middleware 集中處理:

[Authorize(Policy = "CanEditOrders")]
public IActionResult EditOrder(int id)
{
    // 進到這裡代表已通過權限檢查
}

搭配自訂的 IAuthorizationHandler,在請求進來時查詢使用者的角色與對應權限, 與 Policy 要求的權限比對。這樣的好處是權限檢查邏輯集中在一處, Controller 本身保持乾淨,只需要標註需要什麼權限。

動態選單:讓前端也跟著權限走

光是後端擋住 API 還不夠,前端選單如果還是寫死的,使用者會看到自己點不動的選項, 體驗很差。實務作法是把選單結構也存在資料庫,登入時依照使用者的角色權限, 動態組出這個人能看到的選單樹:

// 後端回傳結構範例
{
  "menus": [
    { "title": "訂單管理", "path": "/orders", "icon": "shopping-cart" },
    { "title": "報表中心", "path": "/reports", "icon": "chart-bar" }
  ]
}

前端(例如 Vue.js)拿到這份選單資料後動態渲染導覽列,沒有權限的功能連選單項目都不會出現, 而不是顯示出來但點了跳出「無權限」的錯誤訊息——後者對使用者來說是更差的體驗。

實務上的三個提醒

  1. 權限檢查永遠要在後端做一次: 前端隱藏選單只是體驗優化,真正的安全邊界一定要在 API 層再檢查一次, 不能只靠前端「看不到就等於不能做」。
  2. 權限粒度不要一開始就設計太細: 一開始就想做到「每個欄位都能單獨設定權限」通常是過度工程, 先以「功能模組」為單位設計,真的有需求再往下細分。
  3. 角色與權限的異動要留 Log: 誰在什麼時候調整了誰的權限,這是稽核時經常會被要求提供的紀錄, 建議一開始架構設計時就把操作日誌考慮進去。

結語

RBAC 不是什麼新技術,但把它從一開始就設計進系統架構, 比日後系統長大了才回頭重構權限邏輯,成本低得多。 如果你的系統角色複雜度已經超過三種,現在就是導入 RBAC 的好時機。