PHP user authorization design - using bits.

網路上有人提到這個問題,整理了一下自己提出的回應寫成這篇提供參考。一般做法通常使用資料表紀錄人與權限的關聯,甚至是有群組和角色等更複雜的機制,在檢查是否具有權限時可能要Join許多Table來達成。而此篇文章則以不同的方式來設計,簡單的以位元的方式來表示使用者具有之權限,並使用php與mysql實作範例,範例不包含群組和角色等機制。

首先建立一權限資料表包含index欄位表示第幾個bit,型別為int;name欄位表示權限的名稱,型別為varchar,並建立一些權限,例如:

Table: authorities

indexname
1 Authority1
2 Authority2
... ...

bigint版本:

接著於使用者資料表包含一欄位表示具有之權限,例如authority, 型別為bigint unsigned,並建立一些使用者,例如:

Table: users

idauthority
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是否具有Authority1Authority3權限邏輯如下:

              二進位   十進位
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是否具有Authority1Authority3權限邏輯如下:

               二進位   十進位
     使用者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,以下使用十六進位制表示

idauthority
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)
文章標籤
創作者介紹

小殘的程式光廊

emn178 發表在 痞客邦 PIXNET 留言(0) 人氣()