權限管理設計-基於二進制遮罩的實現

Aug 02, 2025

前言

每次的系統設計都是最有趣的地方,過往用 Laravel 有人寫好的套件,設計了兩個 table rolepermissions,很直覺,也沒什麼不對,仔細想想,每次加權限絕對不是資料本身增加,而是功能誰被允許,所以權限本身應該是跟著code,而非資料。

思考脈絡

應該用一個 enum 限縮權限範圍,一個人會有多個權限,用array是很直覺的方法,如果只用一個數字呢?可以把多個數字合成一個數字的方法

質數

使用質數相乘,用因式分解,就可以還原每一個數字。質數的方法得用除法,要檢查一個質數乘積是否包含一個質數,直接拿過來除,餘數為零,國中數學就可以解決,在程式裡面則使用mod。要把每一個權限都配置一個質數,然後乘起來,檢查的時候用除法,看起來還行....

二進制

把每個權限當作一個 boolean,把它轉成十進制存下來,要使用再還原成二進制。要比對的的時候直接用 AND 就可以知道。

小結

看起來用二進制比較符合需求,用數位對應權限,容易看懂。

設計實作

權限管理系統的核心是將每個權限分配一個唯一的二進制位(bit),並使用位元運算來檢查用戶是否擁有特定權限。以下是設計步驟:

定義權限

使用枚舉(Enum)定義所有可能的權限,每個權限對應一個二進制位。

分配權限值

透過左移運算(<<)為每個權限分配唯一的位元值(例如 1 << 0 表示第一位,1 << 1 表示第二位)。

檢查權限

使用位元 AND 運算(&)檢查用戶的權限值是否包含特定權限。

管理權限

透過位元 OR 運算(|)添加權限,AND NOT 運算(& ~)移除權限。

範例實現

以下是一個基於 TypeScript 的權限管理範例,展示如何定義權限並檢查用戶權限。

enum UserPermission { READ = 1 << 0, // 00000000000001 (1) - 讀取檔案 CREATE = 1 << 1, // 00000000000010 (2) - 建立檔案 UPDATE = 1 << 2, // 00000000000100 (4) - 編輯檔案 DELETE = 1 << 3, // 00000000001000 (8) - 刪除檔案 MOVE = 1 << 4, // 00000000010000 (16) - 移動檔案 DOWNLOAD = 1 << 5, // 00000000100000 (32) - 下載檔案 COPY = 1 << 6, // 00000001000000 (64) - 複製檔案 UPDATE_OCR = 1 << 7, // 00000010000000 (128) - 編輯 OCR CREATE_COMPLAINT = 1 << 8, // 00000100000000 (256) - 生成書狀 DELETE_PERMANENTLY = 1 << 9, // 00001000000000 (512) - 永久刪除 RESTORE = 1 << 10, // 00010000000000 (1024) - 恢復檔案 CONTROL_PRIVATE = 1 << 11, // 00100000000000 (2048) - 機敏檔案控管 FILE_RECLASSIFY = 1 << 12, // 01000000000000 (4096) - 未分類文件調整 CONTROL_NAS = 1 << 13, // 10000000000000 (8192) - 管理 NAS 設定 } /** * 檢查用戶是否擁有指定權限 * @param userPermissions 用戶的權限值(二進制表示) * @param permission 要檢查的權限 * @returns 是否擁有該權限 */ export const hasPermission = ( userPermissions: number, permission: UserPermission, ): boolean => { return (userPermissions & permission) === permission; }; // 範例:檢查用戶是否擁有 FILE_RECLASSIFY 權限 const userPermissions = 16383; // 001111111111111 (包含前 14 位權限) const hasFileReclassify = hasPermission(userPermissions, UserPermission.FILE_RECLASSIFY); console.log(`用戶是否擁有 FILE_RECLASSIFY 權限:${hasFileReclassify}`); // true

程式碼說明

  1. Enum 定義:UserPermission 定義了 14 種權限,每個權限使用左移運算分配唯一的二進制位。例如,READ = 1 << 0 表示 00000000000001(十進制 1),FILE_RECLASSIFY = 1 << 12 表示 01000000000000(十進制 4096)。
  2. 權限檢查:hasPermission 函數使用 & 運算檢查用戶的權限值(userPermissions)是否包含指定的權限。例如,檢查 FILE_RECLASSIFY 時,若 userPermissions & FILE_RECLASSIFY 等於 FILE_RECLASSIFY,則表示用戶擁有該權限。
  3. 範例測試:用戶權限值 16383(二進制 001111111111111)表示擁有前 14 位權限,因此檢查 FILE_RECLASSIFY(第 13 位)會回傳 true。

進階功能

添加權限

透過 OR 運算(|)為用戶添加權限:

let userPermissions = UserPermission.READ; // 初始只有 READ 權限 userPermissions |= UserPermission.CREATE; // 添加 CREATE 權限 console.log(userPermissions); // 3 (00000000000011)

移除權限

透過 AND NOT 運算(& ~)移除用戶權限:

userPermissions &= ~UserPermission.CREATE; // 移除 CREATE 權限 console.log(userPermissions); // 1 (00000000000001)

但實際上前端用 checkbox 丟進來乘在一塊,把直存起來就好

檢查多個權限

檢查用戶是否同時擁有某些權限:

const requiredPermissions = UserPermission.READ | UserPermission.CREATE; const hasAllRequired = (userPermissions & requiredPermissions) === requiredPermissions; console.log(`用戶是否擁有 READ 和 CREATE 權限:${hasAllRequired}`);

注意事項

位元數限制:JavaScript 的數字使用 32 位元或 64 位元(取決於環境),因此最多支援 32 或 64 個權限。若需更多權限,可考慮使用 BigInt 或多個位元欄位。 可讀性與維護:為每個權限提供清晰的命名和註解,避免直接使用魔術數字(magic numbers)。 安全性:確保權限檢查邏輯在伺服器端執行,防止客戶端繞過檢查。 擴展性:新增權限時,應確保新權限的位元值不與現有權限衝突。

結論

本來只是覺得好玩重新設計,後來發現他跟網路常用的 IP 遮罩很像,有碰過電子元件的,就很像只播開關,每一個權限都是一個開關。

Ekman Hsieh

文字工作者,寫作時間常常在人類與電腦之間拉鋸,相信閱讀,相信文字與思想所構築的美麗境界