RBAC(基于角色的權限控制)是一個老話題了,但是這兩天我試圖設計一套表結構實現完整的RBAC時,發現存在很多困難。
我說的完整的RBAC,是指支持角色樹形結構和角色分組。具體來說,應當包含如下權限控制需求:
- 父級角色可以訪問甚至是修改其子級的數據,包含直接子級直到最終子級。
- 角色可以訪問其所在組的數據。
- 父級角色可以訪問其所有子級(從直接子級到最終子級)所在組的數據。
而具體到我的系統中,還應當有如下需求。
- 兼容多種數據庫產品。只能用簡單的表,視圖,存儲過程和函數等實現。
- 同時兼容單條數據處理和批量數據處理的需求。
且不論這些具體需求,RBAC的基本表應當如下四個:
- roleList表,記錄所有的角色和角色組。
- roleId: PK, 角色/組的ID,全局唯一,不區分角色和組。
- roleName:角色/組的名稱。
- roleType: R - 角色,G - 組
- rolePermission表,記錄每一個角色/組對每一個對象的權限。
- permissionID: PK, 無特定意義。
- role: 角色/組的ID。
- object: 對象的ID。
- permission: 權限標識,如讀,寫,刪等。
- roleRelationship表,記錄角色/組之間的關系。
- relationId: PK, 無特定意義。
- superiorRole: 父角色/組的ID。
- role:子角色,子組,成員角色,成員組的ID。
- relationship: 關系標識,可在如下設置集中選取一個。
- PG標識:P - 父子關系,G - 組/成員關系。
- PPGG標識:在PG集上,再加三種:PP - 間接父級關系,GG - 組內組關系,CG - parentRole是組,childRole的子角色或間接子角色是其成員,或其子組(含間接子組)的成員
- objectList表,記錄所有的對象。
- objectId: PK,對象ID,全局唯一。
- objectName: 對象名稱。
- ... ...
分析上述表結構,不難發現,問題的關鍵在于從rolePermission表中讀取數據時,如何限定角色/組的范圍.
方案一
如果角色和組的總量不大,比如在100以內,采用PPGG標識關系,讀取數據時是最快的。這個時候的SQL只需要一個輸入參數?roleId:
SELECT object FROM rolePermission p left join roleRelationship r on p.role = r.role WHERE p.role = ?roleId or r.superiorRole = ?roleId. (尚未驗證SQL的正確性)
但是,這個方案是以極度冗余roleRelationship表的數據為代價的,比如有100個角色,那么roleRelationship中將會有100 * 100 =10,000條記錄。而在每次調整角色和R角色組的時候,就要在roleRelationship中一次增加或刪除100條記錄。這個開銷是比較大的。
方案二
只標識PG,查詢時接收的輸入參數為一個完整的相關角色列表?roleList。
SELECT object FROM rolePermission WHERE role in (?roleList)
在系統運行時,這個?roleList通常可以從role hierarchy cache中取到,比較方便。這個方案的主要問題有二:
1)如果?roleList過長,使用in判斷性能會很差。
2)在有些情況下,如報表查詢和系統外查詢時,取得roleList不太方便。
方案三
只標識PG,但使用如下三個數據庫函數來判斷角色/組之間的關系。
- boolean isChild(role, parentRole) - 如role為parentRole的子,返回true。
- boolean isDescendant(role, ancestorRole) - 如role為ancestorRole的子或間接子級,返回true。
- boolean isMember(role, group) - 如role為group的成員或子組的成員,返回true。
- boolean descendantIsMember(role, group) - 如role的子或間接子級為group的成員,返回true。
- boolean isBelong(role, super) - 如role為super的子,間接子,成員或間接員,或者role的子(含間接子)是super的成員或子組成員,返回true。
在查詢時,也只需要接收一個?roleId:SELECT object FROM rolePemission WHERE isBelong(?roleId, role)
如何寫出高性能的數據庫函數是實現這個方法的關鍵。
上述方法僅是理論分析,我傾向于方案二。
終于想到新的方案了。
方案四,
結合方案一和方案二,在roleRelationship中,對前兩級(也可以是三級或四級)角色,保存其所有的下級角色和組。這樣,如果以前兩級角色查詢數據,就使用方案一,如果以第三級及以下的角色查詢數據,就使用方案二。
仍以100個角色為例,每個角色要保存三個關系:一級主管角色,二級主管角色,直接主管角色,最多有300條數據。
每往角色組中加一個角色,也需要加入三條數據:角色本身,一級主管角色,二級主管角色。
但往角色組中加一個子組,需要加入的數據量就大一些:子組本身,子組所有角色,子組所有角色的一級主管角色和二級主管角色。如在多個子組中發現同一角色,可重復保存,但應在表中附加說明是由哪個子組導入的。這樣在刪除子組時就可以有選擇性的刪除。
但重復子組的情況就比較麻煩,還有等考慮。假充有組g01,g11,g12,g21。g01包含g11和g12,g11和g12分別包含g21。從g01中刪除g11時,如何判斷g21的去留?看來還是應當在維護時判斷應不應當刪除。