Spring Security ACL 核心概念和組件

原文鏈接:https://blog.gaoyuexiang.cn/2020/07/02/spring-security-acl-conception-and-component/
內(nèi)容無差別。

Spring Security 提供了一個 ACL 模塊,也就是 Access Control List,用來做訪問控制。
目的是解決 什么資源什么權(quán)限 的問題。
這里的重點(diǎn)是具體的資源。

面臨的問題

我們通過 Spring Security 的 WebSecurityConfigurerAdapter.configure(HttpSecurity) 方法,
HttpSecurity 對象進(jìn)行配置,只能精確到 API 層面,決定擁有哪些權(quán)限的用戶可以訪問哪些 API,
但是對于哪些用戶對一個具體的資源有訪問權(quán)限卻無能為力。

舉個?? ,對于一個多用戶的博客系統(tǒng),可能存在一個 API /my/drafts ,
可以查看當(dāng)前用戶的草稿,但是不能查看其他用戶的草稿。
那么上面的 HttpSecurity 就無法完成這個需求。

為了解決這個問題,我們可能會有很多解決方法:

  • 實(shí)現(xiàn)一個 AccessDecisionVoter,訪問數(shù)據(jù)庫中的 Blog 記錄,然后判斷當(dāng)前的用戶是否有權(quán)限訪問
  • 實(shí)現(xiàn)一個 AccessDecisionVoter,通過 Authentication 對象中的 GrantedAuthority[] 判斷是否有權(quán)限訪問
  • 拋棄 Spring Security,自己實(shí)現(xiàn)一套權(quán)限控制機(jī)制
  • 將訪問控制的代碼和業(yè)務(wù)代碼組織到一起

這些方法也不是不能用,但都有各自的缺陷(Spring Security ACL 也有,只是比這些要好一點(diǎn)):

  • 第一個方案意味著在 AccessDecisionVoter 中會進(jìn)行數(shù)據(jù)庫訪問,這會造成性能上的隱患
  • 第二個方案在數(shù)據(jù)量上升后可能會面臨 Authentication 對象無比巨大的問題
  • 第三個方案就像自己創(chuàng)造一個加密算法一樣,看起來厲害,但可能會有很多漏洞,畢竟沒有經(jīng)過實(shí)踐檢驗(yàn)
  • 第四個方案是最輕量的,但是很容易破壞代碼的單一職責(zé)原則,最后變成沒人愿意維護(hù)的代碼

ACL 核心概念

針對上面的缺點(diǎn),Spring Security ACL 則是采用了面向 Domain Object 的方式,
抽象出了 Domain Object 這個概念,把 ACL 與業(yè)務(wù)代碼解耦。

我們可以先來看一下 ACL 中的核心概念:

image

除了 Domain Object 和 Security Object,其他概念都是 ACL 中的接口。
這些接口都在 org.springframework.security.acls.model package 中。

Domain Object

Domain Object 是對業(yè)務(wù)類實(shí)例的抽象,用 DDD 的話說,就是對領(lǐng)域模型實(shí)例的抽象。
一個 Domain Object 對應(yīng)的是一個實(shí)例。
它可能是一篇博客,也可能是一條評論。

一個 Domain Object 被一個 ObjectIdentity 唯一標(biāo)識。

因?yàn)橛辛诉@個抽象,我們就可以將 ACL 的代碼和業(yè)務(wù)邏輯解耦,僅僅通過 ObjectIdentity 來標(biāo)識 Acl 和 Domain Object 的關(guān)聯(lián)關(guān)系。

Security Object

Security Object 是對用戶和角色的抽象。代表了一個用戶,或一種角色,或一個權(quán)限組等。
往往是 PrincipalGrantedAuthority 的抽象。

Acl

Acl 類是 ACL 中的核心,也就是 Access Control List 的簡稱。

一個 Acl 實(shí)例擁有一個 ObjectIdentity,標(biāo)識了一個 Domain Object。

一個 Acl 實(shí)例擁有一組 AccessControlEntity,顧名思義,它們就是這個訪問控制列表中的每一個訪問控制項。

一個 Acl 實(shí)例持有一個 Sid 對象,標(biāo)識了這個 Acl 的 owner;owner 對這個 Acl 有完全的控制權(quán)。
所謂完全的控制權(quán),也就是指可以修改、刪除其中的信息,甚至 Acl 本身。

MutableAcl

MutableAcl 是對 Acl 的擴(kuò)展,提供了一些修改 Acl 的方法。

考慮到 Acl 可能更多的被使用到一些不會修改訪問權(quán)限的調(diào)用中,所以它只提供了一些只讀方法。
但有一些可能不太頻繁的會修改訪問權(quán)限的操作,比如創(chuàng)建、刪除等,所以需要一些方法能夠修改 Acl 實(shí)例。
所以擴(kuò)展出了 MutableAcl 類,用來進(jìn)行修改訪問權(quán)限的操作。

ObjectIdentity

前面已經(jīng)提到過 ObjectIdentity 的作用,是用來標(biāo)識 Domain Object,
就是一個脫離業(yè)務(wù)上下文后仍然唯一的 id。
實(shí)際上是 ACL 上下文中對 Domain Object 的抽象

能夠做到這一點(diǎn),是因?yàn)樘峁┝藘蓚€方法:getTypegetIdentifier

type 用來標(biāo)識 Domain Object 的類型,identifier 則是在 type 上下文中唯一的。
不同的 type 下,可以出現(xiàn)相同的 identifier,但 type 卻需要全局唯一。

默認(rèn)實(shí)現(xiàn)是使用 Domain Object 的 java 類全限定名作為 type

identifier 則需要使用者自己實(shí)現(xiàn)。
要求是能根據(jù) Domain Object 找到這個 identifier,否則 Acl 和 Domain Object 就會失去聯(lián)系。
使用 Domain Object 的系統(tǒng) id 會是一個不錯的選擇。

Sid

Sid 是 Security Identity 的簡稱,是在 ACL 上下文中對 Security Object 的抽象。
就像 ObjectIdentity 對 Domain Object 的抽象一樣。

為什么不能統(tǒng)一一下規(guī)范,叫 SecurityIdentity 呢 ???

在實(shí)現(xiàn)上,有 PrincipalSidGrantedAuthoritySid。
前者是對用戶的抽象,后者是對角色、權(quán)限的抽象。

AccessControlEntry

AccessControlEntry 簡稱 ACE,組成了訪問控制列表。
每一個 ACE 標(biāo)識了一個 Sid 的權(quán)限,權(quán)限由 Permission 表示。

?? :

  • 如果一種角色對 Domain Object 有可讀權(quán)限

    • Sid 會是表示這個角色的 GrantedAuthoritySid
    • Permission 會表示 READ 權(quán)限
  • 如果一個用戶對 Domain Object 有可讀、可寫、可邀請協(xié)作的權(quán)限

    • 會有多個 ACE
    • 每個 ACE 的 Sid 都是指向這個用戶的 PrincipalSid
    • 每個 ACE 的 Permission 會不同,分別表示可讀、可寫、可邀請協(xié)作

Permission

Permission 接口可能收到了 Linux 文件權(quán)限的啟發(fā),要求使用 32 位二進(jìn)制數(shù)字來表示權(quán)限。

所以我們可以針對一個 Domain Object 設(shè)計出 232-1 種權(quán)限,應(yīng)該足夠使用了。

小結(jié)

ACL 的核心就是 Acl 類,它將 Domain Object 和 對應(yīng)的 Security Object 以及權(quán)限關(guān)聯(lián)了起來。
其中,將 Security Object 和權(quán)限關(guān)聯(lián)起來的類是 AccessControlEntry。

ACL 權(quán)限驗(yàn)證邏輯

通過了解核心概念,我們知道了 ACL 的核心就是 Acl 類,那么進(jìn)行權(quán)限驗(yàn)證的邏輯也就很明顯了:

  1. 根據(jù)要訪問的對象,得到 ObjectIdentity 實(shí)例
  2. Authentication 中獲取 Sid
  3. 根據(jù) ObjectIdentity 找到對應(yīng)的 Acl
  4. 判斷 ACE 中是否有進(jìn)行訪問需要的 Permission

當(dāng)上面的這個邏輯驗(yàn)證通過時,才會被允許訪問 Domain Object,否則就會出現(xiàn) AccessDeniedException。

獲取 ObjectIdentity 對象

我們先來看看第一步,獲取 ObjectIdentity 對象。

前面介紹 ObjectIdentity 的時候推薦過使用 Domain Object 的 class 和系統(tǒng) id 來作為 ObjectIdentity。
這樣的好處就是我們可以根據(jù) Domain Object 實(shí)例創(chuàng)建出 ObjectIdentity 來。

這個邏輯被抽象成了接口 ObjectIdentityRetrievalStrategy。
它只提供了一個 getObjectIdentity 方法:Object -> ObjectIdentity。

獲取 Sid 對象

因?yàn)?Sid 代表的是用戶和角色,而這些信息被保存在 Authentication 對象的 PrincipalGrantedAuthority[] 中,
所以我們可以通過 Authentication 對象來獲取 Sid。

這個邏輯同樣被抽象成了接口 SidRetrivalStrategy。
它只提供了一個 getSids 方法:Authentication -> List<Sid>

獲取 Acl 對象

ACL 提供了一個接口 AclService 用來獲取 Acl 對象。
接口提供了多種方法,其中被這個邏輯使用到的是 readAclById(ObjectIdentity, List<Sid>)

其實(shí)不提供 List<Sid> 也能查到對應(yīng)的 Acl,但其中就會包含當(dāng)前邏輯中不需要使用到的 ACE。

判斷權(quán)限

Acl 提供了 isGranted 方法用來判斷當(dāng)前的 List<Sid> 是否有需要的權(quán)限。

在默認(rèn)實(shí)現(xiàn) AclImpl 中,判斷的邏輯交給了接口 PermissionGrantingStrategy,
這樣我們可以通過實(shí)現(xiàn)策略而不是 Acl 來達(dá)到重寫驗(yàn)證邏輯的目的。

ACL 驗(yàn)證邏輯的入口

前面描述的驗(yàn)證邏輯,被實(shí)現(xiàn)在了不同的類中。這些類是具體的 security 機(jī)制相關(guān)的類,每一個類都是針對具體 security 機(jī)制的 ACL 驗(yàn)證邏輯的實(shí)現(xiàn)。

針對 pre invocation

前面的文章中,
我們了解過 AccessDecisionVoter 是在實(shí)際調(diào)用發(fā)生前進(jìn)行權(quán)限驗(yàn)證的接口。

ACL 中提供了實(shí)現(xiàn) AclEntryVoter 來實(shí)現(xiàn)驗(yàn)證邏輯。

針對 post invocation

前面的另一篇文章中,
我們了解過 AfterInvocationProvider 是在實(shí)際調(diào)用發(fā)生后進(jìn)行權(quán)限驗(yàn)證的接口。

ACL 中提供了 AbstractAclProvider 來實(shí)現(xiàn)驗(yàn)證邏輯。
它的兩個子類則是針對不同的使用場景,分別實(shí)現(xiàn)權(quán)限驗(yàn)證和 collection filter 的邏輯。

針對 expression based access control

對于使用表達(dá)式的地方,比如 @PostAuthority("hasPermission(returnObject, 'READ')")
ACL 提供了 AclPermissionEvaluator 實(shí)現(xiàn)驗(yàn)證邏輯。

這個類實(shí)現(xiàn)了 PermissionEvaluator 接口。這是 Spring Security 中 hasPermission 表達(dá)式的解析接口。


了解完了這三個入口,我們就知道當(dāng)需要在某個 security 機(jī)制中使用 ACL 時,需要創(chuàng)建出哪一個組件注入到 Spring 容器中。

更新 Acl

前面的驗(yàn)證邏輯是使用場景更多的邏輯,也是對 Acl 進(jìn)行只讀操作的邏輯。
更新 Acl 并不是一個頻繁的操作,但卻是一個必要的操作。
遺憾的是,Spring Security ACL 沒有為我們默認(rèn)實(shí)現(xiàn)更新 Acl 的邏輯,我們需要自己實(shí)現(xiàn)。

好在為了支持更新操作,Spring Security ACL 給我們提供了 MutableAclMutableAclService 這兩個接口,提供了更新 Acl 的方法。

以創(chuàng)建 Acl 為例,我們來看看應(yīng)該寫出什么樣的代碼。(創(chuàng)建 Acl 的場景一般是創(chuàng)建了新的資源時)

public void createAcl(Object domainObject) {
  ObjectIdentity oid = new ObjectIdentityImpl(domainObject);
  Sid sid = new PrincipalSid(SecurityContextHolder.getSecurityContext().getAuthentication());
  Permission p = BasePermission.ADMINISTRATION;
  MutableAcl acl = aclService.createAcl(oid);
  acl.insertAce(acl.getEntries().size(), p, sid, true);
  aclService.updateAcl(acl);
}

對于更新 ACE、刪除 Acl 等操作,MutableAclMutableAclService 都有相應(yīng)的方法,只是和創(chuàng)建一樣,都需要我們自己寫代碼調(diào)用。
這些代碼應(yīng)該和業(yè)務(wù)代碼分隔開,這樣才能滿足 ACL 的初衷,避免寫出難以維護(hù)的代碼。

總結(jié)

本文介紹了 Spring Security ACL 中的核心概念和它們之間的關(guān)系,那張概念圖就是最好的總結(jié)。

另外還介紹了使用 ACL 進(jìn)行權(quán)限控制的邏輯和相關(guān)的組件,也就是那些 strategy 和 service 接口。

最后簡單介紹了更新 Acl 的方法,更詳細(xì)的內(nèi)容會在另外的博客里以 demo 的形式呈現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容