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