[轉載]一種高性能Hierarchical RBAC實現方案

背景算法

 

框圖數據庫

 

上圖中,Role和被設置Permission的Resource都是能夠有任意層級繼承關係的。性能

 

舉例網站

 

舉一個網站的例子來講:orm

 

若是,User表示網站用戶;Role表示角色;Resource表示全部可訪問的URL;Permission是對每個URL的某一個權限(如:查看,修改等)。blog

 

Role能夠有任意層級繼承關係,如:用戶角色能夠分爲Normal User和Admin User,Admin User下又能夠分爲Super Admin、Content Admin、Support Admin等。繼承

 

URL這種Resource也能夠有任意層級繼承關係的,如:對http://abc.com/A/A1/A11/A111.aspx這樣一個連接,能夠認爲http://abc.com/A1是一個URL Resource,http://abc.com/A/A1/A11是他的一個子Resource,http://abc.com/A/A1/A11/A111.aspx又是http://abc.com/A/A1/A11的一個子Resource。遞歸

 

對某一個Role來講,他對某一個Resource – R1的具體的Permissions,等於關聯到這個Role的Resource - R1及其全部父級Resource的Permissions的並集。索引

 

對於某一個User來講,他對某一個Resource的Permissions,等於他所屬的全部Roles的Permissions的並集。get

 

問題

 

各元素之間的關係容易理解,關鍵的難點在於,由於Role和Resource均可以是有無限層級繼承關係的,如何保證權限信息驗證具備較高的性能呢?當繼承關係較複雜時,遞歸檢測的性能無疑是不可接受的。

 

數據庫表

 

User(ID,Name)

Role(ID,Name,ParentID,LeftIndex,RightIndex

UsersInRoles(UserID,RoleID)

Resource(ID,Name,ParentID,LeftIndex,RightIndex

PermissionsOfRole(PermissionsValue,ResourceID,RoleID)

 

這裏簡單起見,對於Permissions,使用一個二進制位表示一個具體的Permission。咱們須要事先定義一個PermissionsValue的每個二進制位表示的Permission。例如:若是PermissionsValue的二進制值爲10101010,表示從低位到高位第二、四、六、8位所表明的權限的並集。

 

使用二進制位表示一個具體的Permission的好處是,處理Permissions的並操做能夠轉換爲二進制的OR;缺點是,具體的Permission想不能特別多,由於多一個就意味着PermissionsValue的最大值大一個2的次方。8位二進制的最大值是2的8次方,這不算很大,可是,1000位二進制的最大值是2的1000次方,這就是個不可想象的巨大數字了。好在,通常來說,具體的Permission項目不會特別多的。

 

該方案的關鍵,就在於Role和Resource表的LeftIndex和RightIndex這兩個字段了,咱們將使用這兩個字段,在避免遞歸的狀況下,實現較高性能的取某個繼承節點的全部子元素或全部父元素的算法。

 

算法

 

咱們以Role爲例,首先Role表中有且只有一條記錄存放全部Roles的頂層父節點(1,「Root Role」,1,2)。當他沒有子節點時,其LeftIndex和RightIndex的值分別爲1和2。當對其插入子節點時,LeftIndex和RightIndex的值須要作相應的調整,調整的規則以下(括號中爲LeftIndex和RightIndex的值):

 

按逆時針方向,你們能看出規則嗎?

按照這個規則,咱們能夠以下獲取某一個節點的全部字節點或全部父結點(使用僞SQL代碼表示):

 

獲取ID爲3的Role節點的全部的子結點包括自己:

SELECT * FROM Role WHERE

LeftIndex >= (SELECT LeftIndex FROM Role WHERE ID = 3)

AND

RightIndex <= (SELECT RightIndex FROM Role WHERE ID = 3)

注:若是要不包括ID=3的節點自己,只須要用>和<代替>=和<=。

 

獲取ID爲5的Role節點的全部父節點包括自己:

SELECT * FROM Role WHERE

LeftIndex <= (SELECT LeftIndex FROM Role WHERE ID = 3)

AND

RightIndex >= (SELECT RightIndex FROM Role WHERE ID = 3)

注:若是要不包括ID=5的節點自己,只須要用>和<代替>=和<=。

 

你們能夠根據上面的圖驗證一下算法的效果。徹底不須要遞歸,只須要簡單的判斷LeftIndex和RightIndex就行,性能天然是很是好的。

 

咱們甚至能夠以很是簡單的SQL語句得到某一個ID爲2的User對ID爲6的Resource的PermissionsValue:

 

DECLARE @PermissionsValue int;

SELECT @PermissionsValue = @PermissionsValue | PermissionsValue

FROM PermissionsOfRole WHERE

RoleID IN

(

SELECT ID FROM Role WHERE

LeftIndex >= (SELECT LeftIndex FROM Role WHERE ID IN

(SELECT RoleID FROM UsersInRoles WHERE UserID = 2))

AND

RightIndex <= (SELECT RightIndex FROM Role WHERE ID IN

(SELECT RoleID FROM UsersInRoles WHERE UserID = 2))

)

AND

ResourceID IN

(

SELECT ID FROM Resource WHERE

LeftIndex <= (SELECT LeftIndex FROM Resource WHERE ID = 6)

AND

RightIndex >= (SELECT RightIndex FROM Resource WHERE ID = 6)

);

SELECT @PermissionsValue;

上面的SQL雖然有很多嵌套的SELECT,可是,由於子查詢基本上都是對主鍵字段的條件判斷,LeftIndex和RightIndex咱們也會加上索引,所以,實際上不會對性能形成太大影響。

 

OK,查詢性能很好,不過這是以新建或修改Role和Resource的層級關係時的必定的性能損失爲代價的。每次新增或修改Role或Resource的層級關係時,必須按照前面所述的規則重置全部節點的LeftIndex和RightIndex值。不過,通常狀況下,因爲Role和Resource的維護操做佔系統總體操做的比例很小,幾乎能夠忽略,所以其性能損失也不是什麼大問題。具體的重置全部節點LeftIndex和RightIndex值的僞代碼我就不貼出來了,你們稍微花費幾個腦細胞就能想出來了^-^

//結束

相關文章
相關標籤/搜索