好久沒(méi)有更新文章了……這一年過(guò)得太忙。
準(zhǔn)備一篇個(gè)人認(rèn)為值得拿出來(lái)分享的文章真的需要很多時(shí)間,如果你喜歡,請(qǐng)?jiān)u論、點(diǎn)贊讓我知道,我會(huì)抽更多的時(shí)間來(lái)更新一些分享給大家,謝謝!
此篇文章主要嘗試將世面上現(xiàn)有的一些權(quán)限系統(tǒng)設(shè)計(jì)做一下簡(jiǎn)單的總結(jié)分析,個(gè)人水平有限,如有錯(cuò)誤請(qǐng)不吝指出。
術(shù)語(yǔ)
這里對(duì)后面會(huì)用到的詞匯做一個(gè)說(shuō)明,老司機(jī)請(qǐng)直接翻到常見(jiàn)設(shè)計(jì)模式。
用戶
發(fā)起操作的主體。
對(duì)象(Subject)
指操作所針對(duì)的客體對(duì)象,比如訂單數(shù)據(jù)或圖片文件。
權(quán)限控制表 (ACL: Access Control List)
用來(lái)描述權(quán)限規(guī)則或用戶和權(quán)限之間關(guān)系的數(shù)據(jù)表。
權(quán)限 (Permission)
用來(lái)指代對(duì)某種對(duì)象的某一種操作,例如“添加文章的操作”。
權(quán)限標(biāo)識(shí)
權(quán)限的代號(hào),例如用“ARTICLE_ADD”來(lái)指代“添加文章的操作”權(quán)限。
常見(jiàn)設(shè)計(jì)模式
自主訪問(wèn)控制(DAC: Discretionary Access Control)
系統(tǒng)會(huì)識(shí)別用戶,然后根據(jù)被操作對(duì)象(Subject)的權(quán)限控制列表(ACL: Access Control List)或者權(quán)限控制矩陣(ACL: Access Control Matrix)的信息來(lái)決定用戶的是否能對(duì)其進(jìn)行哪些操作,例如讀取或修改。
而擁有對(duì)象權(quán)限的用戶,又可以將該對(duì)象的權(quán)限分配給其他用戶,所以稱之為“自主(Discretionary)”控制。
這種設(shè)計(jì)最常見(jiàn)的應(yīng)用就是文件系統(tǒng)的權(quán)限設(shè)計(jì),如微軟的NTFS。
DAC最大缺陷就是對(duì)權(quán)限控制比較分散,不便于管理,比如無(wú)法簡(jiǎn)單地將一組文件設(shè)置統(tǒng)一的權(quán)限開(kāi)放給指定的一群用戶。

強(qiáng)制訪問(wèn)控制(MAC: Mandatory Access Control)
MAC是為了彌補(bǔ)DAC權(quán)限控制過(guò)于分散的問(wèn)題而誕生的。在MAC的設(shè)計(jì)中,每一個(gè)對(duì)象都都有一些權(quán)限標(biāo)識(shí),每個(gè)用戶同樣也會(huì)有一些權(quán)限標(biāo)識(shí),而用戶能否對(duì)該對(duì)象進(jìn)行操作取決于雙方的權(quán)限標(biāo)識(shí)的關(guān)系,這個(gè)限制判斷通常是由系統(tǒng)硬性限制的。比如在影視作品中我們經(jīng)常能看到特工在查詢機(jī)密文件時(shí),屏幕提示需要“無(wú)法訪問(wèn),需要一級(jí)安全許可”,這個(gè)例子中,文件上就有“一級(jí)安全許可”的權(quán)限標(biāo)識(shí),而用戶并不具有。
MAC非常適合機(jī)密機(jī)構(gòu)或者其他等級(jí)觀念強(qiáng)烈的行業(yè),但對(duì)于類似商業(yè)服務(wù)系統(tǒng),則因?yàn)椴粔蜢`活而不能適用。

基于角色的訪問(wèn)控制(RBAC: Role-Based Access Control)
因?yàn)镈AC和MAC的諸多限制,于是誕生了RBAC,并且成為了迄今為止最為普及的權(quán)限設(shè)計(jì)模型。
RBAC在用戶和權(quán)限之間引入了“角色(Role)”的概念(暫時(shí)忽略Session這個(gè)概念):

圖片來(lái)自Apache Directory
如圖所示,每個(gè)用戶關(guān)聯(lián)一個(gè)或多個(gè)角色,每個(gè)角色關(guān)聯(lián)一個(gè)或多個(gè)權(quán)限,從而可以實(shí)現(xiàn)了非常靈活的權(quán)限管理。角色可以根據(jù)實(shí)際業(yè)務(wù)需求靈活創(chuàng)建,這樣就省去了每新增一個(gè)用戶就要關(guān)聯(lián)一遍所有權(quán)限的麻煩。簡(jiǎn)單來(lái)說(shuō)RBAC就是:用戶關(guān)聯(lián)角色,角色關(guān)聯(lián)權(quán)限。另外,RBAC是可以模擬出DAC和MAC的效果的。
例如數(shù)據(jù)庫(kù)軟件MongoDB便是采用RBAC模型,對(duì)數(shù)據(jù)庫(kù)的操作都劃分成了權(quán)限(MongoDB權(quán)限文檔):
| 權(quán)限標(biāo)識(shí) | 說(shuō)明 |
|---|---|
| find | 具有此權(quán)限的用戶可以運(yùn)行所有和查詢有關(guān)的命令,如:aggregate、checkShardingIndex、count等。 |
| insert | 具有此權(quán)限的用戶可以運(yùn)行所有和新建數(shù)據(jù)有關(guān)的命令:insert和create等。 |
| collStats | 具有此權(quán)限的用戶可以對(duì)指定database或collection執(zhí)行collStats命令。 |
| viewRole | 具有此權(quán)限的用戶可以查看指定database的角色信息。 |
| … |
基于這些權(quán)限,MongoDB提供了一些預(yù)定義的角色(MongoDB預(yù)定義角色文檔,用戶也可以自己定義角色):
| 角色 | find | insert | collStats | viewRole | … |
|---|---|---|---|---|---|
| read | ? | ? | … | ||
| readWrite | ? | ? | ? | … | |
| dbAdmin | ? | ? | … | ||
| userAdmin | ? | … |
最后授予用戶不同的角色,就可以實(shí)現(xiàn)不同粒度的權(quán)限分配了。
目前市面上絕大部分系統(tǒng)在設(shè)計(jì)權(quán)限系統(tǒng)時(shí)都采用RBAC模型。然而也有的系統(tǒng)錯(cuò)誤地實(shí)現(xiàn)了RBAC,他們采用的是判斷用戶是否具有某個(gè)角色而不是判斷權(quán)限,例如以下代碼:
<?php
if ($user->hasRole('hr')) {
// 執(zhí)行某種只有“HR”角色才能做的功能,例如給員工漲薪…
// ...
}
如果后期公司規(guī)定部門經(jīng)理也可以給員工漲薪,這時(shí)就不得不修改代碼了。
以上基本就是RBAC的核心設(shè)計(jì)(RBAC Core)。而基于核心概念之上,RBAC規(guī)范還提供了擴(kuò)展模式。
角色繼承(Hierarchical Role)

帶有角色繼承的RBAC。圖片來(lái)自Apache Directory
顧名思義,角色繼承就是指角色可以繼承于其他角色,在擁有其他角色權(quán)限的同時(shí),自己還可以關(guān)聯(lián)額外的權(quán)限。這種設(shè)計(jì)可以給角色分組和分層,一定程度簡(jiǎn)化了權(quán)限管理工作。
職責(zé)分離(Separation of Duty)
為了避免用戶擁有過(guò)多權(quán)限而產(chǎn)生利益沖突,例如一個(gè)籃球運(yùn)動(dòng)員同時(shí)擁有裁判的權(quán)限(看一眼就給你判犯規(guī)狠不狠?),另一種職責(zé)分離擴(kuò)展版的RBAC被提出。
職責(zé)分離有兩種模式:
- 靜態(tài)職責(zé)分離(Static Separation of Duty):用戶無(wú)法同時(shí)被賦予有沖突的角色。
- 動(dòng)態(tài)職責(zé)分離(Dynamic Separation of Duty):用戶在一次會(huì)話(Session)中不能同時(shí)激活自身所擁有的、互相有沖突的角色,只能選擇其一。

靜態(tài)職責(zé)分離。圖片來(lái)自Apache Directory

動(dòng)態(tài)職責(zé)分離。圖片來(lái)自Apache Directory
講了這么多RBAC,都還只是在用戶和權(quán)限之間進(jìn)行設(shè)計(jì),并沒(méi)有涉及到用戶和對(duì)象之間的權(quán)限判斷,而在實(shí)際業(yè)務(wù)系統(tǒng)中限制用戶能夠使用的對(duì)象是很常見(jiàn)的需求。例如華中區(qū)域的銷售沒(méi)有權(quán)限查詢?nèi)A南區(qū)域的客戶數(shù)據(jù),雖然他們都具有銷售的角色,而銷售的角色擁有查詢客戶信息的權(quán)限。
那么我們應(yīng)該怎么辦呢?
用戶和對(duì)象的權(quán)限控制
在RBAC標(biāo)準(zhǔn)中并沒(méi)有涉及到這個(gè)內(nèi)容(RBAC基本只能做到對(duì)一類對(duì)象的控制),但是這里講幾種基于RBAC的實(shí)現(xiàn)方式。
首先我們看看PHP框架Yii 1.X的解決方案(2.X中代碼更為優(yōu)雅,但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');
// 主要看這里。
// 這里創(chuàng)建了一個(gè)名為`updateOwnPost`的權(quán)限,并且寫了一段代碼用來(lái)檢驗(yàn)用戶是否為該帖子的作者
$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');
實(shí)現(xiàn)效果:

圖片來(lái)自Yii官方WiKi
在這個(gè)Yii的官方例子中,updateOwnPost在判斷用戶是否具有updatePost權(quán)限的基礎(chǔ)上更進(jìn)一步判斷了用戶是否有權(quán)限操作這個(gè)特定的對(duì)象,并且這個(gè)判斷邏輯是通過(guò)代碼設(shè)置的,非常靈活。
不過(guò)大部分時(shí)候我們并不需要這樣的靈活程度,會(huì)帶來(lái)額外的開(kāi)發(fā)和維護(hù)成本,而另一種基于模式匹配規(guī)則的對(duì)象權(quán)限控制可能更適合。例如判斷用戶是否對(duì)Id為123的文章具有編輯的權(quán)限,代碼可能是這樣的:
<?php
// 假設(shè)articleId是動(dòng)態(tài)獲取的
$articleId = 123;
if ($user->can("article:edit:{$articleId}")) {
// ...
}
而給用戶授權(quán)則有多種方式可以選擇:
<?php
// 允許用戶編輯Id為123的文章
$user->grant('article:edit:123');
// 使用通配符,允許用戶編輯所有文章
$user->grant('article:edit:*');
雖然不及Yii方案的靈活,但某些場(chǎng)景下這樣就夠用了。
如果大家還有更好的方案,歡迎在評(píng)論中提出。
基于屬性的權(quán)限驗(yàn)證(ABAC: Attribute-Based Access Control)
ABAC被一些人稱為是權(quán)限系統(tǒng)設(shè)計(jì)的未來(lái)。
不同于常見(jiàn)的將用戶通過(guò)某種方式關(guān)聯(lián)到權(quán)限的方式,ABAC則是通過(guò)動(dòng)態(tài)計(jì)算一個(gè)或一組屬性來(lái)是否滿足某種條件來(lái)進(jìn)行授權(quán)判斷(可以編寫簡(jiǎn)單的邏輯)。屬性通常來(lái)說(shuō)分為四類:用戶屬性(如用戶年齡),環(huán)境屬性(如當(dāng)前時(shí)間),操作屬性(如讀取)和對(duì)象屬性(如一篇文章,又稱資源屬性),所以理論上能夠?qū)崿F(xiàn)非常靈活的權(quán)限控制,幾乎能滿足所有類型的需求。
例如規(guī)則:“允許所有班主任在上課時(shí)間自由進(jìn)出校門”這條規(guī)則,其中,“班主任”是用戶的角色屬性,“上課時(shí)間”是環(huán)境屬性,“進(jìn)出”是操作屬性,而“校門”就是對(duì)象屬性了。為了實(shí)現(xiàn)便捷的規(guī)則設(shè)置和規(guī)則判斷執(zhí)行,ABAC通常有配置文件(XML、YAML等)或DSL配合規(guī)則解析引擎使用。XACML(eXtensible Access Control Markup Language)是ABAC的一個(gè)實(shí)現(xiàn),但是該設(shè)計(jì)過(guò)于復(fù)雜,我還沒(méi)有完全理解,故不做介紹。
總結(jié)一下,ABAC有如下特點(diǎn):
- 集中化管理
- 可以按需實(shí)現(xiàn)不同顆粒度的權(quán)限控制
- 不需要預(yù)定義判斷邏輯,減輕了權(quán)限系統(tǒng)的維護(hù)成本,特別是在需求經(jīng)常變化的系統(tǒng)中
- 定義權(quán)限時(shí),不能直觀看出用戶和對(duì)象間的關(guān)系
- 規(guī)則如果稍微復(fù)雜一點(diǎn),或者設(shè)計(jì)混亂,會(huì)給管理者維護(hù)和追查帶來(lái)麻煩
- 權(quán)限判斷需要實(shí)時(shí)執(zhí)行,規(guī)則過(guò)多會(huì)導(dǎo)致性能問(wèn)題
既然ABAC這么好,那最流行的為什么還是RBAC呢?
我認(rèn)為主要還是因?yàn)榇蟛糠窒到y(tǒng)對(duì)權(quán)限控制并沒(méi)有過(guò)多的需求,而且ABAC的管理相對(duì)來(lái)說(shuō)太復(fù)雜了。Kubernetes便因?yàn)锳BAC太難用,在1.8版本里引入了RBAC的方案。
ABAC有時(shí)也被稱為PBAC(Policy-Based Access Control)或CBAC(Claims-Based Access Control)。
結(jié)語(yǔ)
權(quán)限系統(tǒng)設(shè)計(jì)可謂博大精深,這篇文章只是介紹了一點(diǎn)皮毛。
隨著人類在信息化道路上越走越遠(yuǎn),權(quán)限系統(tǒng)的設(shè)計(jì)也在不斷創(chuàng)新,但目前好像處在了平臺(tái)期。
可能因?yàn)樵赗BAC到ABAC之間有著巨大的鴻溝,無(wú)法輕易跨越,也可能是一些基于RBAC的微創(chuàng)新方案還不夠規(guī)范化從而做到普及。不過(guò)在服務(wù)化架構(gòu)的浪潮下,未來(lái)這一塊必然有極高的需求,也許巨頭們已經(jīng)開(kāi)始布局了。
參考文檔
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