PHP user authorization design - using bits.
網路上有人提到這個問題,整理了一下自己提出的回應寫成這篇提供參考。一般做法通常使用資料表紀錄人與權限的關聯,甚至是有群組和角色等更複雜的機制,在檢查是否具有權限時可能要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: 0000 0111 (7) Authority2: 0000 0010 (2) AND: 0000 0010
AND結果為 0000 0010,與權限位元一致,則表示具有權限。
case 2:
要檢驗使用者2是否具有Authority2權限邏輯如下:
二進位 十進位 使用者2: 0000 0001 (1) Authority2: 0000 0010 (2) AND: 0000 0000
AND結果為 0000 0000,則表示不具有權限。
case 3:
要檢驗使用者1是否具有Authority1和Authority3權限邏輯如下:
二進位 十進位 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權限邏輯如下:
二進位 十進位 使用者2: 0000 0001 (1) Authorities: 0000 0101 AND: 0000 0001
權限OR部分與case3相同,接著與使用者權限AND結果為 0000 0001,與權限位元不一致,則表示不具有權限。
由於bigint佔64個bit,所以此版本最多支援64種權限,而此範例的使用者3權限18446744073709551615相當於所有bit皆為1,也就是具有所有權限。
實際的使用PHP實作
<?php $result = mysql_query('SELECT * FROM authorities'); while ($row = mysql_fetch_assoc($result)) $authorities[$row['name']] = 1 << ($row['index'] - 1); // 先將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'])); // 判斷多個權限,將權限作OR 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; } ?>
輸出
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相同,但是此版本可超過64bit長度的限制,可自訂varbinary長度,換成blob型別亦可。
實際的使用PHP實作
<?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'])); // 可超過64 // 判斷多個權限,此版本使用index array,於function中處理OR 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); // 將index array轉成byte array與處理OR運算 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; } ?>
輸出
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)