取這個標題,可能有點膨脹,畢竟看到“架構”這個詞,很多人想到的可能是大專案,涉及到分散式,高併發等一些高大上的東西。
前段時間分別用vue和react寫了兩個後臺管理系統的模板vue-quasar-admin和3YAdmin。兩個專案中都實現了基於RBAC的許可權控制。因為本職工作是後端開發,比較清楚許可權控制一個管理系統應該必須具備的核心功能,而且是可以做到通用的。打算寫寫關於管理系統前後端分離方面的文章,也是做一個知識的總結,其中會涉及到vue,react,node,.net core等方面的知識。
術語描述
- 使用者(Subject):發起操作的主體
- 物件(Object):指操作所針對的客體物件,比如文章或評論
- 許可權(Permission):用來指代對某種物件的某一種操作,例如“新增文章的操作”
- 許可權碼:許可權的代號,例如用“ARTICLE_ADD”來指代“新增文章的操作”許可權
許可權有時候也可以稱為動作或者功能。比如“新增文章”,既可以認為它是一個動作,也可以認為它是一個功能。物件也可以稱為資源。
常用的許可權模型
- ACL(Access Control List)(訪問控制列表)
- DAC(Discretionary Access Control)(自主訪問控制)
- MAC(Mandatory Access Control)(強制訪問控制)
- RBAC(Role-Based Access Control)(基於角色的訪問控制)
- ABAC(Attribute-Based Access Control)(基於屬性的訪問控制)
ACL(Access Control List)(訪問控制列表)
ACL是最早也是最基本的一種訪問控制機制,它是用來描述使用者和許可權之間關係的資料列表。它的原理非常簡單:每一項資源,都配有一個列表,這個列表記錄的就是哪些使用者可以對這項資源執行CRUD等操作。當試圖訪問這項資源時,會首先檢查這個列表中是否有關於當前使用者的訪問許可權,從而確定當前使用者可否執行相應的操作。
例如一個檔案物件的 ACL 為 Alice: read,write; Bob: read,這代表 Alice 對該檔案既能讀又能寫,而 Bob 只能讀取。
由於ACL的簡單性,使得它幾乎不需要任何基礎設施就可以完成訪問控制。但同時它的缺點也是很明顯的,由於需要維護大量的訪問許可權列表,ACL在效能上有明顯的缺陷。另外,對於擁有大量使用者與眾多資源的應用,管理訪問控制列表本身就變成非常繁重的工作。
最開始的ACL定義中,使用者直接和許可權掛鉤,資料儲存的是使用者與許可權的關聯關係。如果兩個使用者的許可權是一樣的,那麼就需要分別儲存這兩個使用者與許可權的關聯關係,也是上面所提到的ACL的缺陷。為了解決這些問題,便有了對ACL設計的改進,相同許可權的使用者放到同一個分組裡,分組與許可權掛鉤,不再是使用者直接與許可權掛鉤。以及後來出現的RBAC(基於角色的訪問控制),角色與分組也是差不多的概念,角色直接與許可權掛鉤,使用者再與角色進行關聯。
所以,現在一般說ACL,不再是使用者直接和許可權掛鉤的一種許可權控制模型,把它看做一個單純的訪問控制列表即可。列表裡維護的可能是使用者與許可權的關係,也可以是使用者組與許可權的關係,也可以是角色與許可權的關係,甚至是部門,職位等等於許可權的關係。
ACL是許可權體系中的業務規則。RBAC等許可權模型要用到ACL才能工作,ACL服務於RBAC等許可權模型,其它許可權控制體系裡的許可權規則也叫ACL。
DAC(Discretionary Access Control)(自主訪問控制)
系統會識別使用者,然後根據被操作物件(Subject)的許可權控制列表(ACL: Access Control List)或者許可權控制矩陣(ACL: Access Control Matrix)的資訊來決定使用者的是否能對其進行哪些操作,例如讀取或修改。 而擁有物件許可權的使用者,又可以將該物件的許可權分配給其他使用者,所以稱之為“自主(Discretionary)”控制。
因為使用者能自主地將自己擁有的許可權授予其他使用者,所以DAC模型可以任意傳遞許可權,使用者能間接獲得本不具有的訪問許可權,因此DAC模型的安全性較低,不能給系統充分的資料保護。
DAC可以直接使用ACL的物理模型,區別在於,DAC模型中使用者可以將自己具備的許可權分配給其它使用者(程式裡的操作就是根據使用者ID篩選出許可權列表,根據列表為要分配許可權的使用者構造出新的許可權列表並儲存)
DAC是傳統的UNIX訪問控制模型,也是Windows檔案系統的訪問控制模型。
Windows的檔案訪問許可權的設定中,除了使用者,還有組。這個組與後面要說到的RABC模型的角色有什麼區別呢?
stackoverflow.com/questions/7…
我認為沒必要去劃分的太清楚,不管是組還是角色,都是為了更好的管理和分配許可權在最原始的ACL模型上做的改進。如果有需要,甚至可以把許可權分配到部門,職位上。
MAC(Mandatory Access Control)(強制訪問控制)
MAC是為了彌補DAC許可權控制過於分散的問題而誕生的。在MAC的設計中,每一個物件都有一些許可權標識,每個使用者同樣也會有一些許可權標識,而使用者能否對該物件進行操作取決於雙方的許可權標識的關係,這個限制判斷通常是由系統硬性限制的。訪問時,系統先對使用者的訪問許可級別和資源物件的密級進行比較,再決定使用者是否可以訪問資源物件。使用者不能改變自身和資源物件的安全級別,只有系統管理員或管理程式才能 控制資源物件和使用者的級別。比如在影視作品中我們經常能看到特工在查詢機密檔案時,螢幕提示需要“無法訪問,需要一級安全許可”,這個例子中,檔案上就有“一級安全許可”的許可權標識,而使用者並不具有。
MAC非常適合機密機構或者其他等級觀念強烈的行業,但對於類似商業服務系統,則因為不夠靈活而不能適用。
MAC可以繼續使用DAC的模型,但是要對使用者進行等級劃分,比如一級,二級,三級。。。,對物件資源也要做劃分,比如機密,祕密和最高機密。使用者訪問的資源的時候,根據使用者等級與資源訪問級別來做判斷,比如一級使用者只能訪問機密檔案,如果訪問的是最高機密檔案,系統就會拒絕。這一系列規則是優先於DAC的,如果MAC與DAC混用,要先校驗MAC再校驗DAC。
RBAC(Role-Based Access Control)(基於角色的訪問控制)
ACL的訪問控制機制中,直接維護的是使用者與功能的關係,這一系列的關係就是一個許可權列表。當很多的使用者具有相同功能許可權的時候,就要進行繁瑣的關聯操作。RBAC就是在使用者與許可權之間引入了角色的概念。使用者與角色之間做關聯,許可權列表維護的是角色與功能的關係。
RBAC是目前使用最普遍的許可權控制模型。當某些使用者具備相同的許可權的時候,只需要為這些使用者建一個角色,把相應的功能關聯到這個角色上,生成角色的許可權列表。當有新的使用者需要相同許可權的時候,把使用者關聯到這個角色上即可。而當用檢查或校驗使用者的操作許可權的時候,查詢使用者所屬角色的許可權列表即可。
當然,RBAC也不是完美的,比如想要為某個使用者單獨設定某個功能許可權,可能需要為這個功能許可權單獨建立一個角色,然後把特定的使用者關聯到這個角色上。當想要移除某個使用者的特定功能許可權的時候,可能需要重新設定角色的功能許可權,把特定功能許可權從當前角色中移除,建立新的角色並關聯特定的功能許可權,然後再把新角色與相關的使用者做關聯(也可以直接在特定功能的程式裡校驗操作使用者)
這裡說一個比較常見的RBAC的錯誤的用法:那就是直接使用角色做許可權判斷。比如只有角色A才能做文章的刪除操作。
function delPost(postId){
if(!isRole('A')){
return false;
}
}
複製程式碼
如果需求該為角色B也可以刪除文章。那就必須修改程式碼
function delPost(postId){
if(!isRole('A')&&!isRole('B')){
return false;
}
}
複製程式碼
正確的做法應該是新增"刪除文章"這個功能,把這個功能關聯到相應的角色上。判斷的時候是根據功能去判斷而不是角色。
function delPost(postId){
if(!hasPermission('POST_DEL')){
return false;
}
}
複製程式碼
針對“只有角色A才能做文章的刪除操作”這一需求,把這個刪除功能關聯到角色A上,然後把需要這個操作許可權的使用者加入到角色A中即可。當別的角色也需要這個操作許可權,把功能關聯到對應角色上即可,不需要再修改程式碼。
在RBAC的核心基礎上,還可以做相應的擴充套件,比如角色繼承,角色分組之類的,這些擴充套件都是為了在一定程度簡化許可權管理工作。
ABAC(Attribute-Based Access Control)(基於屬性的許可權控制)
RBAC雖然是目前最普遍的許可權控制模型。但是某些情況下,RBAC是無法滿足並且也實現不了的。比如業務員1和業務員2都屬於業務員角色,都有檢視客戶訂單的許可權。當有一個需求,要求業務員1只能檢視北京地區的客戶的訂單,業務員2只能檢視上海的客戶的訂單。這單單使用RBAC是無法實現。藉助RBAC,可行的做法是,分地區建立角色,然後程式中根據角色做資料的過濾,這種做法缺點之前也提到過,需求變更的時候可能需要每次都修改程式碼。
上面業務員檢視訂單的例子,地區是訂單的一個屬性,需求就是針對這個地區屬性來做訂單的查詢範圍的許可權控制。這種許可權控制方式就是ABAC(Attribute-Based Access Control)(基於屬性的許可權控制),也被一些人稱為是許可權系統設計的未來。
不同於常見的將使用者通過某種方式關聯到許可權的方式,ABAC則是通過動態計算一個或一組屬性是否滿足某種條件來進行授權判斷的(可以編寫簡單的邏輯)。屬性通常來說分為四類:使用者屬性(如使用者年齡),環境屬性(如當前時間),操作屬性(如讀取)和物件屬性(如一篇文章,又稱資源屬性),所以理論上能夠實現非常靈活的許可權控制,幾乎能滿足所有型別的需求。
例如規則:“允許所有班主任在上課時間自由進出校門”這條規則,其中,“班主任”是使用者的角色屬性,“上課時間”是環境屬性,“進出”是操作屬性,而“校門”就是物件屬性了。
ABAC非常的靈活,但是實現也是非常的難。這其中涉及到邏輯的動態執行,資料動態過濾等,更加具體就是動態拼接SQL語句(使用ORM的話就是動態組裝對應ORM的查詢語句)。
感興趣的可以在Github上搜尋ABAC,看看不同語言是否已經有現成的解決方案。下面說說我學習到的一種實現方式:
還是業務員檢視訂單的例子,在RBAC的基礎上,擴充套件一個實體規則,訂單就是實體,也就是針對訂單設定一系列的規則。規則儲存格式可以是json也可以是xml,甚至是Sql語句,能解析即可。比如北京地區這個規則:
{
"regionId":1
}
複製程式碼
上海地區:
{
"regionId":3
}
複製程式碼
regionId
就是系統裡對應區域的Id,也是訂單或訂單相關表的某個欄位。
儲存這個規則的時候,規則內容(就是上面的json),規則實體(也就是訂單,表明這個規則是針對訂單的)是必須的。也可以加上這個規則是適用增刪改查中的一種或多種。
建立好實體的規則,將規則與角色做關聯,也就是將北京地區的規則關聯到北京地區角色上,上海地區的規則關聯到上海地區角色上。
後端做許可權校驗的時候,還是先按RBAC模型的控制方式進行校驗(是否具備訂單檢視許可權),然後根據當前操作物件(也就是實體),取出使用者所屬角色關聯的對應實體的規則。然後解析規則,動態拼接Sql或者ORM語句。
沒做地區限制(或沒配置規則)的時候,Sql可能是
select userId,orderNo,createdDate from T_Order
複製程式碼
配置了規則,解析拼接後可能就是
select userId,orderNo,createdDate from T_Order where regionId=1
複製程式碼
這裡是針對地區這個屬性實現了動態的許可權控制。實際開發過程中,要控制的東西是非常多了,檢視欄位的控制,資料範圍的控制。要滿足這些複雜的控制,需要制定一套完整的規則,以及針對規則編寫相應的解析程式。比如根據配置的規則,最後解析出來可能是各種Sql語句:<,>,=,like,in,not in等等。
可以看出,要真正的落地實現ABAC是多麼的複雜。每次都要解析規則,對程式的效能也造成的影響,就算使用快取,命中的概率也是非常的小,因為很多因素都是動態的。
所以,如果需要根據屬性做許可權判斷的場景不是很多的話,還是建議使用RBAC,然後程式中做判斷比較省事省力。
總結
ACL早期定義中是一種許可權控制機制,這種機制直接維護的是使用者與功能的關係,功能就是針對物件定義的一些操作,比如增刪改查的等。使用者與功能的關係列表也稱為許可權列表或訪問控制列表,現在說ACL,一般就是指這個許可權列表或訪問控制列表,但是裡面維護的關係不一定是使用者與功能的關係,在RBAC中維護的就是角色與功能的關係。
RBAC在ACL的基礎上加入了角色的概念,許可權列表或訪問控制列表裡維護的不再是使用者與功能的關係,而是角色與功能的關係。ACL可以和RBAC混著用,既可以在角色上設定許可權,也可以直接給使用者設定許可權,更加靈活。藉助角色的思想,可以在使用者組,組織,職位等等上設定許可權,以便更好的做好許可權管理,也就是將許可權設定從單一個體轉移到某一類組合上。
ABAC非常的靈活,也非常的難實現。