網路上有人提到這個問題,整理了一下自己提出的回應寫成這篇提供參考。一般做法通常使用資料表紀錄人與權限的關聯,甚至是有群組和角色等更複雜的機制,在檢查是否具有權限時可能要 Join 許多 Table 來達成。而此篇文章則以不同的方式來設計,簡單的以位元的方式來表示使用者具有之權限,並使用 php 與 mysql 實作範例,範例不包含群組和角色等機制。
首先建立一權限資料表包含 index 欄位表示第幾個 bit,型別為 int
;name 欄位表示權限的名稱,型別為 varchar
,並建立一些權限,例如:
Table: authorities
index | name |
---|
1 | Authority1 |
2 | Authority2 |
… | … |
bigint 版本
接著於使用者資料表包含一欄位表示具有之權限,例如 authority, 型別為 bigint unsigned
,並建立一些使用者,例如:
Table: users
id | authority |
---|
1 | 7 |
2 | 1 |
3 | 18446744073709551615 |
Case 1
要檢驗使用者1是否具有 Authority2 權限邏輯如下:
1 2 3 4
| 二進位 十進位 使用者1: 0000 0111 (7) Authority2: 0000 0010 (2) AND: 0000 0010
|
AND 結果為 0000 0010,與權限位元一致,則表示具有權限。
Case 2
要檢驗使用者2 是否具有 Authority2 權限邏輯如下:
1 2 3 4
| 二進位 十進位 使用者2: 0000 0001 (1) Authority2: 0000 0010 (2) AND: 0000 0000
|
AND結果為 0000 0000,則表示不具有權限。
Case 3
要檢驗使用者1 是否具有 Authority1 和 Authority3 權限邏輯如下:
1 2 3 4 5 6 7 8 9
| 二進位 十進位 Authority1: 0000 0001 (1) Authority3: 0000 0100 (3) OR: 0000 0101
二進位 十進位 使用者1: 0000 0111 (7) Authorities: 0000 0101 AND: 0000 0101
|
要同時判斷是否具有多個權限時,先將所有權限作位元運算之 OR,結果為 0000 0101;接著再與使用者權限作 AND,結果仍然為 0000 0101,則表示具有權限。
Case 4
要檢驗使用者2 是否具有 Authority1 和 Authority3 權限邏輯如下:
1 2 3 4
| 二進位 十進位 使用者2: 0000 0001 (1) Authorities: 0000 0101 AND: 0000 0001
|
權限 OR 部分與 case3 相同,接著與使用者權限 AND 結果為 0000 0001,與權限位元不一致,則表示不具有權限。
由於 bigint
佔 64 個 bit,所以此版本最多支援 64 種權限,而此範例的使用者3 權限 18446744073709551615 相當於所有 bit 皆為 1,也就是具有所有權限。
實際的使用 PHP 實作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php $result = mysql_query('SELECT * FROM authorities'); while ($row = mysql_fetch_assoc($result)) $authorities[$row['name']] = 1 << ($row['index'] - 1);
$result = mysql_query('SELECT * FROM users'); echo "<pre>"; while ($row = mysql_fetch_assoc($result)) { echo "user" . $row['id'] . ":\n"; var_dump(has_authority($row['authority'], $authorities['Authority1'])); var_dump(has_authority($row['authority'], $authorities['Authority2'])); var_dump(has_authority($row['authority'], $authorities['Authority4'])); var_dump(has_authority($row['authority'], $authorities['Authority1'] | $authorities['Authority3'])); var_dump(has_authority($row['authority'], $authorities['Authority1'] | $authorities['Authority4'])); echo "\n"; } echo "</pre>";
function has_authority($user_authority, $authority) { return ($user_authority & $authority) == $authority; } ?>
|
輸出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| user1: bool(true) bool(true) bool(false) bool(true) bool(false)
user2: bool(true) bool(false) bool(false) bool(false) bool(false)
user3: bool(true) bool(true) bool(true) bool(true) bool(true)
|
varbinary 版本
使用者資料表之 authority 型別改成使用 varbinary
,以下使用十六進位制表示
id | authority |
---|
1 | 01 |
2 | 07 |
3 | FF FF FF FF FF FF FF FF |
4 | FF FF FF FF FF FF FF FF 01 |
邏輯與 bigint
相同,但是此版本可超過 64 bit 長度的限制,可自訂 varbinary
長度,換成 blob
型別亦可。
實際的使用 PHP 實作
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 49 50
| <?php $result = mysql_query('SELECT * FROM authorities'); while ($row = mysql_fetch_assoc($result)) $authorities[$row['name']] = $row['index'];
$result = mysql_query('SELECT * FROM users');
echo "<pre>"; while ($row = mysql_fetch_assoc($result)) { echo "user" . $row['id'] . ":\n"; var_dump(has_authority($row['authority'], $authorities['Authority1'])); var_dump(has_authority($row['authority'], $authorities['Authority2'])); var_dump(has_authority($row['authority'], $authorities['Authority4'])); var_dump(has_authority($row['authority'], $authorities['Authority65'])); var_dump(has_authority($row['authority'], array($authorities['Authority1'], $authorities['Authority3']))); var_dump(has_authority($row['authority'], array($authorities['Authority1'], $authorities['Authority4']))); echo "\n"; } echo "</pre>";
function has_authority($user_authority, $indexes) { if (!is_array($indexes)) $indexes = array($indexes);
foreach ($indexes as $index) { $byte_index = ceil(($index) / 8) - 1; $authority = 1 << (($index - 1) % 8); if (isset($authorities[$byte_index])) $authorities[$byte_index] |= $authority; else $authorities[$byte_index] = $authority; }
foreach ($authorities as $byte_index => $authority) { $byte = ord($user_authority[$byte_index]); if ($byte == 0) return false;
if (($authority & $byte) != $authority) return false; }
return true; } ?>
|
輸出
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
| user1: bool(true) bool(true) bool(false) bool(false) bool(true) bool(false)
user2: bool(true) bool(false) bool(false) bool(false) bool(false) bool(false)
user3: bool(true) bool(true) bool(true) bool(false) bool(true) bool(true)
user4: bool(true) bool(true) bool(true) bool(true) bool(true) bool(true)
|