好久沒有更新文章了……這一年過得太忙。
準備一篇個人認為值得拿出來分享的文章真的需要很多時間,如果你喜歡,請評論、點贊讓我知道,我會抽更多的時間來更新一些分享給大家,謝謝!
此篇文章主要嘗試將世面上現有的一些權限系統設計做一下簡單的總結分析,個人水平有限,如有錯誤請不吝指出。
術語
這里對后面會用到的詞匯做一個說明,老司機請直接翻到常見設計模式。
用戶
發起操作的主體。
對象(Subject)
指操作所針對的客體對象,比如訂單數據或圖片文件。
權限控制表 (ACL: Access Control List)
用來描述權限規則或用戶和權限之間關系的數據表。
權限 (Permission)
用來指代對某種對象的某一種操作,例如“添加文章的操作”。
權限標識
權限的代號,例如用“ARTICLE_ADD”來指代“添加文章的操作”權限。
常見設計模式
自主訪問控制(DAC: Discretionary Access Control)
系統會識別用戶,然后根據被操作對象(Subject)的權限控制列表(ACL: Access Control List)或者權限控制矩陣(ACL: Access Control Matrix)的信息來決定用戶的是否能對其進行哪些操作,例如讀取或修改。
而擁有對象權限的用戶,又可以將該對象的權限分配給其他用戶,所以稱之為“自主(Discretionary)”控制。
這種設計最常見的應用就是文件系統的權限設計,如微軟的NTFS。
DAC最大缺陷就是對權限控制比較分散,不便于管理,比如無法簡單地將一組文件設置統一的權限開放給指定的一群用戶。
強制訪問控制(MAC: Mandatory Access Control)
MAC是為了彌補DAC權限控制過于分散的問題而誕生的。在MAC的設計中,每一個對象都都有一些權限標識,每個用戶同樣也會有一些權限標識,而用戶能否對該對象進行操作取決于雙方的權限標識的關系,這個限制判斷通常是由系統硬性限制的。比如在影視作品中我們經常能看到特工在查詢機密文件時,屏幕提示需要“無法訪問,需要一級安全許可”,這個例子中,文件上就有“一級安全許可”的權限標識,而用戶并不具有。
MAC非常適合機密機構或者其他等級觀念強烈的行業,但對于類似商業服務系統,則因為不夠靈活而不能適用。
基于角色的訪問控制(RBAC: Role-Based Access Control)
因為DAC和MAC的諸多限制,于是誕生了RBAC,并且成為了迄今為止最為普及的權限設計模型。
RBAC在用戶和權限之間引入了“角色(Role)”的概念(暫時忽略Session這個概念):
圖片來自Apache Directory
如圖所示,每個用戶關聯一個或多個角色,每個角色關聯一個或多個權限,從而可以實現了非常靈活的權限管理。角色可以根據實際業務需求靈活創建,這樣就省去了每新增一個用戶就要關聯一遍所有權限的麻煩。簡單來說RBAC就是:用戶關聯角色,角色關聯權限。另外,RBAC是可以模擬出DAC和MAC的效果的。
例如數據庫軟件MongoDB便是采用RBAC模型,對數據庫的操作都劃分成了權限(MongoDB權限文檔):
權限標識 | 說明 |
---|---|
find | 具有此權限的用戶可以運行所有和查詢有關的命令,如:aggregate、checkShardingIndex、count等。 |
insert | 具有此權限的用戶可以運行所有和新建數據有關的命令:insert和create等。 |
collStats | 具有此權限的用戶可以對指定database或collection執行collStats命令。 |
viewRole | 具有此權限的用戶可以查看指定database的角色信息。 |
… |
基于這些權限,MongoDB提供了一些預定義的角色(MongoDB預定義角色文檔,用戶也可以自己定義角色):
角色 | find | insert | collStats | viewRole | … |
---|---|---|---|---|---|
read | ? | ? | … | ||
readWrite | ? | ? | ? | … | |
dbAdmin | ? | ? | … | ||
userAdmin | ? | … |
最后授予用戶不同的角色,就可以實現不同粒度的權限分配了。
目前市面上絕大部分系統在設計權限系統時都采用RBAC模型。然而也有的系統錯誤地實現了RBAC,他們采用的是判斷用戶是否具有某個角色而不是判斷權限,例如以下代碼:
<?php
if ($user->hasRole('hr')) {
// 執行某種只有“HR”角色才能做的功能,例如給員工漲薪…
// ...
}
如果后期公司規定部門經理也可以給員工漲薪,這時就不得不修改代碼了。
以上基本就是RBAC的核心設計(RBAC Core)。而基于核心概念之上,RBAC規范還提供了擴展模式。
角色繼承(Hierarchical Role)
帶有角色繼承的RBAC。圖片來自Apache Directory
顧名思義,角色繼承就是指角色可以繼承于其他角色,在擁有其他角色權限的同時,自己還可以關聯額外的權限。這種設計可以給角色分組和分層,一定程度簡化了權限管理工作。
職責分離(Separation of Duty)
為了避免用戶擁有過多權限而產生利益沖突,例如一個籃球運動員同時擁有裁判的權限(看一眼就給你判犯規狠不狠?),另一種職責分離擴展版的RBAC被提出。
職責分離有兩種模式:
- 靜態職責分離(Static Separation of Duty):用戶無法同時被賦予有沖突的角色。
- 動態職責分離(Dynamic Separation of Duty):用戶在一次會話(Session)中不能同時激活自身所擁有的、互相有沖突的角色,只能選擇其一。
靜態職責分離。圖片來自Apache Directory
動態職責分離。圖片來自Apache Directory
講了這么多RBAC,都還只是在用戶和權限之間進行設計,并沒有涉及到用戶和對象之間的權限判斷,而在實際業務系統中限制用戶能夠使用的對象是很常見的需求。例如華中區域的銷售沒有權限查詢華南區域的客戶數據,雖然他們都具有銷售的角色,而銷售的角色擁有查詢客戶信息的權限。
那么我們應該怎么辦呢?
用戶和對象的權限控制
在RBAC標準中并沒有涉及到這個內容(RBAC基本只能做到對一類對象的控制),但是這里講幾種基于RBAC的實現方式。
首先我們看看PHP框架Yii 1.X的解決方案(2.X中代碼更為優雅,但1.X的示例代碼更容易看明白):
<?php
$auth=Yii::app()->authManager;
$auth->createOperation('createPost','create a post');
$auth->createOperation('readPost','read a post');
$auth->createOperation('updatePost','update a post');
$auth->createOperation('deletePost','delete a post');
// 主要看這里。
// 這里創建了一個名為`updateOwnPost`的權限,并且寫了一段代碼用來檢驗用戶是否為該帖子的作者
$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);
$task->addChild('updatePost');
$role=$auth->createRole('reader');
$role->addChild('readPost');
$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
實現效果:
圖片來自Yii官方WiKi
在這個Yii的官方例子中,updateOwnPost
在判斷用戶是否具有updatePost
權限的基礎上更進一步判斷了用戶是否有權限操作這個特定的對象,并且這個判斷邏輯是通過代碼設置的,非常靈活。
不過大部分時候我們并不需要這樣的靈活程度,會帶來額外的開發和維護成本,而另一種基于模式匹配規則的對象權限控制可能更適合。例如判斷用戶是否對Id為123的文章具有編輯的權限,代碼可能是這樣的:
<?php
// 假設articleId是動態獲取的
$articleId = 123;
if ($user->can("article:edit:{$articleId}")) {
// ...
}
而給用戶授權則有多種方式可以選擇:
<?php
// 允許用戶編輯Id為123的文章
$user->grant('article:edit:123');
// 使用通配符,允許用戶編輯所有文章
$user->grant('article:edit:*');
雖然不及Yii方案的靈活,但某些場景下這樣就夠用了。
如果大家還有更好的方案,歡迎在評論中提出。
基于屬性的權限驗證(ABAC: Attribute-Based Access Control)
ABAC被一些人稱為是權限系統設計的未來。
不同于常見的將用戶通過某種方式關聯到權限的方式,ABAC則是通過動態計算一個或一組屬性來是否滿足某種條件來進行授權判斷(可以編寫簡單的邏輯)。屬性通常來說分為四類:用戶屬性(如用戶年齡),環境屬性(如當前時間),操作屬性(如讀取)和對象屬性(如一篇文章,又稱資源屬性),所以理論上能夠實現非常靈活的權限控制,幾乎能滿足所有類型的需求。
例如規則:“允許所有班主任在上課時間自由進出校門”這條規則,其中,“班主任”是用戶的角色屬性,“上課時間”是環境屬性,“進出”是操作屬性,而“校門”就是對象屬性了。為了實現便捷的規則設置和規則判斷執行,ABAC通常有配置文件(XML、YAML等)或DSL配合規則解析引擎使用。XACML(eXtensible Access Control Markup Language)是ABAC的一個實現,但是該設計過于復雜,我還沒有完全理解,故不做介紹。
總結一下,ABAC有如下特點:
- 集中化管理
- 可以按需實現不同顆粒度的權限控制
- 不需要預定義判斷邏輯,減輕了權限系統的維護成本,特別是在需求經常變化的系統中
- 定義權限時,不能直觀看出用戶和對象間的關系
- 規則如果稍微復雜一點,或者設計混亂,會給管理者維護和追查帶來麻煩
- 權限判斷需要實時執行,規則過多會導致性能問題
既然ABAC這么好,那最流行的為什么還是RBAC呢?
我認為主要還是因為大部分系統對權限控制并沒有過多的需求,而且ABAC的管理相對來說太復雜了。Kubernetes便因為ABAC太難用,在1.8
版本里引入了RBAC的方案。
ABAC有時也被稱為PBAC(Policy-Based Access Control)或CBAC(Claims-Based Access Control)。
結語
權限系統設計可謂博大精深,這篇文章只是介紹了一點皮毛。
隨著人類在信息化道路上越走越遠,權限系統的設計也在不斷創新,但目前好像處在了平臺期。
可能因為在RBAC到ABAC之間有著巨大的鴻溝,無法輕易跨越,也可能是一些基于RBAC的微創新方案還不夠規范化從而做到普及。不過在服務化架構的浪潮下,未來這一塊必然有極高的需求,也許巨頭們已經開始布局了。
參考文檔
Red Hat: Multi-Level Security (MLS)
冰云:An Introduction To Role-Based Access Control
NIST: Role-Based Access Control
Stackoverflow: Group vs role Any real difference?
Yii: Getting to Understand Hierarchical RBAC Scheme
Role-Based Access Control in Computer Security