1. 前言
相比 Web 請求的安全及方法調(diào)用級別的安全,有些應(yīng)用還會定義更加復(fù)雜的訪問權(quán)限。在這種情況下,權(quán)限策略需要同時包含:
- 「who」通過認(rèn)證(Authentication)完成;
- 「where」在什么地方應(yīng)用;
- 「what」安全對象是什么。
也就是說,權(quán)限策略除了考慮調(diào)用的方法,還有考慮調(diào)用域?qū)ο蟮膶?shí)例。
舉例說明。假設(shè)我們設(shè)計(jì)一個寵物診所的管理系統(tǒng),該系統(tǒng)有兩個主要用戶組:工作人員和客戶。員工可以訪問所有動物數(shù)據(jù),而客戶只能查看自己的數(shù)據(jù)。假設(shè)我們?yōu)樵撓到y(tǒng)擴(kuò)展了新的功能,即客戶可以授權(quán)其他用戶查看自己的數(shù)據(jù),比如其他寵物醫(yī)院的關(guān)聯(lián)機(jī)構(gòu),寵物俱樂部等等。在 Spring Security 項(xiàng)目中,我們有幾種實(shí)現(xiàn)方法:
-
在業(yè)務(wù)方法中實(shí)現(xiàn)安全策略。
比如,我們可以在「客戶」的域?qū)ο髮?shí)例中放置集合,通過權(quán)限的配置內(nèi)容,判斷集合中哪個用戶擁有訪問權(quán)限。這種方式下,我們使用
SecurityContextHolder.getContext().getAuthentication()
方式獲取權(quán)限對象。 -
自定義訪問決策實(shí)例
AccessDecisionVoter
并配和GrantedAuthority
對象。擴(kuò)展實(shí)現(xiàn)
AccessDecisionVoter
,通過Authentication
對象中GrantedAuthority[]
集合的內(nèi)容實(shí)現(xiàn)安全策略。這種方式下,我們需要在權(quán)限對象GrantedAuthority
體現(xiàn)出該主體是否有對其他「客戶」的訪問權(quán)限。 -
自定義訪問決策實(shí)例
AccessDecisionVoter
直接通過「客戶」域?qū)ο髮?shí)例判定「客戶」權(quán)限。這種情況下
AccessDecisionVoter
對象需要有檢索「客戶」的數(shù)據(jù)訪問接口。
上述的方法都是適用的。但是,第一種方式中,授權(quán)檢查的代碼將會和業(yè)務(wù)代碼緊密關(guān)聯(lián),耦合度高,不便于單元測試。適用 GrantedAuthority
的方式的缺點(diǎn)是需要對每一個「客戶」實(shí)例進(jìn)行權(quán)限判斷,當(dāng)「客戶」數(shù)量很大時,這種做法執(zhí)行效率會降低。第三種做法相當(dāng)于直接從外部獲取「客戶」的全部信息,相對前兩種效果更好一些,即實(shí)現(xiàn)了代碼分離,又降低了內(nèi)存和計(jì)算量的消耗但是「客戶」對象被暴露了多次,第一次在權(quán)限判定時,第二次在業(yè)務(wù)邏輯時,這樣同樣降低了效率。同時,這三種方式都需要我們從頭開始編碼,所以這些方式都不是最佳方式。
Spring Security 為我們提供了一種便捷的域?qū)ο蟀踩芾聿呗裕竟?jié)主要討論域?qū)ο蟮臋?quán)限策略。
Spring Security 的域?qū)ο蟀踩珜?shí)現(xiàn)是通過 「ACLs(access control list)服務(wù)」方式實(shí)現(xiàn)。使用 Spring Security ACLs 服務(wù),需要導(dǎo)入 spring-security-acl-xxx.jar
依賴包。
Spring Security 域?qū)ο蟀踩δ芤?ACL 的概念為核心,系統(tǒng)中每一個域?qū)ο髮?shí)例都擁有各自的 ACL 配置表,該 ACL 記錄著該域訪問者的黑白名單列表。Spring Security 的 ACL 有三個主要操作:
- 查詢和修改所有域?qū)ο蟮?ACL 配置
- 在方法調(diào)用前,確保其主體參數(shù)可以被進(jìn)行權(quán)限判定;
- 在方法調(diào)用后,確保其主體返回可以被進(jìn)行權(quán)限判斷。
這種方法的優(yōu)勢在于 ACL 的存儲和檢索的高效性。系統(tǒng)中域?qū)ο蟮拿總€實(shí)例都可能被多次訪問,ACL 提供了高性能的查詢能力、可插拔、最小化死鎖的數(shù)據(jù)庫修改操作、代碼獨(dú)立及完整的封裝。
2.1 ACL 的存儲
以數(shù)據(jù)庫方式為例,使用數(shù)據(jù)庫作為 ACL 存儲時,需要用到四個數(shù)據(jù)表:
-
ACL_SID
系統(tǒng)中任何身份或者權(quán)限信息,都有一個 SID,即他的安全唯一標(biāo)識。該表包含列「ID」,文本類型,用于存儲 SID 值;和一個標(biāo)志列「Flag」,用來描述該 SID 是身份或是權(quán)限。因此,每一個身份或者權(quán)限都只有一條數(shù)據(jù),用來獲取授權(quán),SID 也被稱為「接收者(recipient)」
-
ACL_CLASS
用于標(biāo)識域?qū)ο箢愋?。包含?ID 和域?qū)ο蟮?Java 類名。每一個域?qū)ο箢惷挥幸粭l ACL 記錄。
-
ACL_OBJECT_IDENTITY
保存著系統(tǒng)里的所有域?qū)ο髮?shí)例,包含列「ID」、「ACL_CLASS.ID」、「ACL_SID.ID」。
-
ACL_ENTRY
保存著獨(dú)立的許可記錄。包含外鍵「ACL_OBJECT_IDENTITY.ID」,標(biāo)識列表示是否允許或者拒絕,標(biāo)識的格式是二進(jìn)制的位掩碼形式。
ACL_ENTRY 中的掩碼位標(biāo)志著是否允許被訪問。默認(rèn)情況下0位代表讀、1位代表寫、2位代表創(chuàng)建、3位代表刪除、4位代表執(zhí)行。
2.2 ACL 主要對象和接口
- ACL。每個域?qū)ο蠖加星覂H有一個「ACL」對象,該對象保持了
AccessControlEntry
及「ACL」的所有者?!窤CL」不直接引用域?qū)ο?,而是引?ObjectIdentity
,存儲在ACL_OBJECT_IDENTITY
表中。 - AccessControlEntry。「ACL」中包含多個
AccessControlEntry
對象,在框架中被簡寫成ace
。每個ace
關(guān)聯(lián)Permission
、Sid
、ACL
的實(shí)例。ace
可以標(biāo)記為許可,也可以標(biāo)記為不允許,被存儲在ACL_ENTRY
表中。 - Permission。權(quán)限表示一個特定的不可變的位掩碼,具有匹配權(quán)限和信息輸出的功能?;緳?quán)限策略(0 位~4 位)包含在
BasePermission
類中。 - Sid?!窤CL」模塊需要用到用戶的身份信息和權(quán)限信息。這些信息通過 Sid (Security identity)定位。常見的身份信息 Sid 類如
PrincipalSid
和GrantedAuthoritySid
。這些信息存儲在ACL_SID
表中。 - ObjectIdentity。每個域?qū)ο笤凇窤CL」模塊內(nèi)部用
ObjectIdentity
表示。默認(rèn)實(shí)現(xiàn)類為ObjectIdentityImpl
。 - AclService。檢索適用于給定
ObjectIdentity
的Acl
實(shí)例。其實(shí)現(xiàn)類有JDBCAclService
等,檢索操作委托給LookupStrategy
完成。LookupStrategy
為檢索「ACL」信息提供了一種高度優(yōu)化的策略,使用批處理檢索的方式「BasicLookupStrategy」,并支持利用視圖、分級查詢及其他高性能方案的「non-ANSI SQL」方式實(shí)現(xiàn)。 - MutableAclService。允許「ACL」被修改變動。該接口如果不是必須的。
注意:現(xiàn)有的 AclService
及其數(shù)據(jù)庫相關(guān)類,使用的都是 ANSI-SQL
。
3. 代碼演示
Spring Security 官方提供了兩個實(shí)例,它們演示了ACL模塊。第一個是關(guān)于聯(lián)系人的演示,第二個是文檔管理系統(tǒng)(DMS)案例。
使用 Spring Security ACL 功能的第一步,是確定 ACL 數(shù)據(jù)的存儲位置。這里需要實(shí)例化 DataSource
,并將其注入到 JdbcMutableAclService
和 BasicLookupStrategy
實(shí)例中。前者提供了修改的接口,后者用于提高「ACL」檢索效能。
當(dāng)上述內(nèi)容完成實(shí)例化之后,接下來我們需要確保域模型和 Spring Security ACL 的連通性。多數(shù)情況下域?qū)ο蠖及?public Serializable getId()
方法,用來返回域?qū)ο蟮奈ㄒ粯?biāo)識。
關(guān)于如何創(chuàng)建「ACL」或者修改現(xiàn)有「ACL」請看以下代碼:
// 為 ACE 準(zhǔn)備基本數(shù)據(jù)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;
// 創(chuàng)建 ACL 對象
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}
// 通過 ACE 授予更多權(quán)限
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
該實(shí)例中,演示了如何檢索標(biāo)識符為 44
的類型為 Foo
的域?qū)ο?。而后我們?chuàng)造了「ACE」,是名為「Samantha」的主體可以訪問和管理該對象。實(shí)例中 insertAce
方法的作用是插入條目,其最后一個 bool 值即為「允許」或「拒絕」,通常情況下,我們使用白名單「ACL」方式。
完成了上述內(nèi)容后,我們需要在數(shù)據(jù)庫中維護(hù)好「ACL」信息,并將「ACL」信息作為授權(quán)決策邏輯的一部分來使用。
一旦您使用了上述技術(shù)在數(shù)據(jù)庫中存儲一些ACL信息,下一步就是實(shí)際使用ACL信息作為授權(quán)決策邏輯的一部分。這一步實(shí)現(xiàn)方式有很多,比如擴(kuò)展 AccessDecisionInvestor
或者 AfterInvocationProvider
,可以分別在方法執(zhí)行前后觸發(fā)鑒權(quán)。這些方法使用 AclService
檢索「ACL」,然后調(diào)用 Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
決定是允許還是拒絕。同樣也可以使用 AclEntryVoter
,AclEntryAfterInvocationProvider
,AclEntryAfterInvocationCollectionFilteringProvider
類,所有這些類都提供了基于聲明的方式去獲取 ACL 信息,所以不需要我們每次修改權(quán)限代碼。
4. 小結(jié)
本節(jié)討論了域?qū)ο蟮陌踩渲貌呗裕饕獌?nèi)容有:
- Spring Security 通過 ACL 方式實(shí)現(xiàn)高性能域?qū)ο蟮臋?quán)限控制;
- Spring Security ACL 鑒權(quán)有基于關(guān)系型數(shù)據(jù)庫的成熟解決方案;
- Spring Security ACL 模塊降低了執(zhí)行效率,也降低了開發(fā)工作量。
至此,關(guān)于權(quán)限部分的討論告一段落,從下節(jié)開始,我們討論 Spring Security 除了「認(rèn)證」和「鑒權(quán)」之外的常用操作。