24.1.?概述
請注意:在2.0.0之前,Spring Security稱為Acegi
Security。老的Acegi Security提供了一個ACL模塊,放在org.[acegisecurity/springsecurity].acl
包下。這個老的現在已經被拋棄,并可能會在將來的
Spring Security
發行版中去掉。本章討論新的
ACL
模塊,官方推薦在
Spring Security 2.0.0或更高版本中使用,它被置于org.springframework.security.acls
包下。
復雜的應用常常會有需要定義訪問權限——不只是在web請求和方法調用水平的控制。而是安全決議需要由“who (Authentication
), where (MethodInvocation
) and what (SomeDomainObject
)”構成。也就是說,授權決議還要考慮方法調用中的實際域對象。
想象你正在為寵物診所設計一個應用。你的基于Spring的應用主要有兩組用戶:寵物診所的職員和客戶。職員可以訪問所有數據,但客戶只能看到自己的客戶記錄。為了使這個例子更加有趣一點,你的客戶可以讓其它客戶看他們的客戶記錄,例如他們"puppy preschool"的顧問或本地"Pony Club"的總裁。以Spring Security為基礎,你有幾個方式可以選擇:
1.?????
編寫一個自己的業務方法強制安全控制。你可以從Customer域對象中獲取一個有訪問權限用戶的集合??梢杂?code>SecurityContextHolder.getContext().getAuthentication()來訪問
Authentication
對象。
2.?????
編寫一個
AccessDecisionVoter
,通過
Authentication
中儲存的
GrantedAuthority[]
來控制安全檢查。這意味著你的
AuthenticationManager
要把代表用戶對域對象訪問權限的
GrantedAuthority
[]填充到Authentication
對象中去。
3.?????
編寫一個AccessDecisionVoter
來強制安全控制,直接打開目標
Customer
域對象。這意味著你的
voter
需要訪問
DAO
來獲取
Customer
對象。它接著會訪問
Customer
對象集合來進行安全決議。
這些方法都是合情合理的。但是,第一個方法把你的授權檢查和業務代碼耦合到一起了。帶來的主要問題包括提高了單元測試的難度,以及Customer
授權邏輯很難在其它地方重用。從
Authentication
中獲取GrantedAuthority[]
是好的,但是面對數量很大的
Customer
s時也是不行的。如果用戶訪問的Customer
s數量可能達到5,000個(在這個例子里不大可能,但是想象如果是一個很大的Pony Club里的獸醫?。?,構建Authentication
需要耗費的內存和時間可能不是你想要的。最后一種方法,編寫代碼從外部直接打開
Customer
,可能是這
3
個方法里最好的一個。它成功分離了關注點,也沒有濫用內存和
CPU
,但還是效率很低,
AccessDecisionVoter
和最終的業務方法都要自己來調用
DAO
方法來獲取
Customer
對象。一次業務方法調用要訪問兩次
DAO
很明顯也不是你想要的。另外,上面提到的每一種方法你都要自己從頭來編寫自己的訪問控制列表(
ACL
)持久化和業務邏輯。
幸運地,還有另外一個選擇,下面我們來討論一下這個選擇。
24.2.?關鍵概念
Spring Security的ACL服務發布為spring-security-acl-xxx.jar
。要使用
Spring Security的域對象安全,你要把這個包加入到你的classpath中。
Spring Security的域對象安全以訪問控制列表(ACL)為核心概念。你系統里的每個域對象都有自己的ACL,這個ACL記錄了誰能或不能訪問該域對象。Spring Security為你的應用帶來3個主要的ACL相關能力。
·???????? 提供一條可以高效地為你所有的域對象獲取ACL條目(以及修改ACL)的途徑;
·???????? 提供一條在方法被調用前保證給定用戶有訪問你的域對象許可的途徑;
·???????? 提供一條在方法被調用后保證給定用戶有訪問你的域對象許可的途徑;
正如第一點所說,Spring Security的ACL模塊的主要能力之一是提供一個高性能獲取ACL的途徑。這個ACL repository的能力是非常重要的,因為你系統中的每一個域對象可能有若干個訪問控制條目,每個ACL又可能是從其他ACL繼承而來——樹型結構(這很常用,Spring Security提供了很容易使用的支持)。Spring Security的ACL模塊已經很小心的考慮了設計以滿足高性能的ACL檢索,它利用了可插接的緩存模塊,死鎖最小化的數據庫更新,獨立于ORM框架(我們使用直接JDBC),適當的包裝,以及透明的數據庫更新。
數據庫的設計是以ACL模塊的操作為中心的,讓我們來看看該模塊的實現里缺省使用的4個主要數據表。下面列出在一個典型的Spring Security ACL部署中的數據表,以數據量大小排序,行數最多的在最后面:
·????????
ACL_SID使我們能夠在系統中唯一定義任何principal或authority(“SID”表示“Security IDentity”)。主鍵是ID,SID的文字表示,以及一個表示該SID是principal名還是GrantedAuthority
的標識。每個
principal
或
GrantedAuthority
都有一個對應的記錄。當在接受許可的上下文中使用的時候,
SID
通常被稱為“接收者(
recipient)”。
·???????? ACL_CLASS使我們能夠在系統中唯一定義任何域對象類。主鍵是ID,然后是CLASS(java類名)。每個我們想要為其存儲ACL許可的類有一個記錄。
·???????? ACL_OBJECT_IDENTITY存儲系統里每個域對象實例的信息。其中的列包括ID;一個到表ACL_CLASS 的外鍵OBJECT_ID_CLASS;一個使我們知道是為哪個ACL_CLASS類實例提供信息的唯一標識OBJECT_ID_IDENTITY;父對象,一個到ACL_SID的外鍵OWNER_SID表示該域對象的所有者;ENTRIES_INHERITING表示是否允許ACL條目從ACL父條目繼承。
·???????? 最后,ACL_ENTRY存儲為每個recipient分配的權限。它的列包括一個到表ACL_OBJECT_IDENTITY的外鍵,recipient(例如一個到ACL_SID的外鍵),是否需要審計,以及一個表示許可是授予還是拒絕的“整數位掩碼”(許可可能包括多個方面,如view, edit, delete)。每個recipient對每個域對象的許可都有一個記錄
上一段中提到,ACL系統使用了“整數位掩碼”。不要擔心,你不需要知道這些數字指向的具體位移來使用ACL系統,只需要知道我們有32個開關可以打開或關閉就可以了(一個int數值4個byte,共32個bits)。每一個位代表一個permission,缺省地,這些permission是:讀(bit 0),寫(bit 1),創建(bit 2),刪除(bit 3)和管理(bit 4)。如果你要使用其他permission,可以很容易的實現自己的permission實例,ACL框架的其余部分不需要具備你定義的擴展的知識,即可完成操作。
明白我們采用這種“整數位掩碼”對你的系統中域對象的數量是完全沒有影響這一點是很重要的。我們的permission只能使用32個bit,但是你可以有數以十億計的域對象(這也意味著會有數以十億計的記錄在ACL_OBJECT_IDENTITY表中,ACL_ENTRY表的記錄則更多)。我們特別說明這一點是因為發現有些用戶錯誤的以為我們要為每個潛在的域對象使用一個bit,這是不對的。
現在我們已經提供了一個ACL系統的基本概覽,以及在表結構上看起來的樣子,現在讓我們來看看關鍵接口。關鍵接口列表如下:
·????????
Acl:每一個域對象有且僅有一個ACL的對象,它內部擁有AccessControlEntry
,并知道Acl的所有者。Acl不直接指向域對象,而是指向一個ObjectIdentity
。
Acl
是存儲在
ACL_OBJECT_IDENTITY表中的。
·????????
AccessControlEntry
:一個
Acl
包含多個
AccessControlEntry
,在ACL框架中,我們常常把它簡稱為ACE。每個ACE都指向一組Permission,Sid和Acl。ACE可以是granting或non-granting的,同時包含有審計的相關設置。ACE保存在ACL_ENTRY表中。
·????????
Permission
:
Permission
表示一個不可變的位掩碼,并為位掩碼機制和信息輸出提供方便的功能。上面提到的
5
個基本的
Permission
(bits 0 到 4)包含在
BasePermission
類中。
·????????
Sid
:
ACL
模塊需要參照到
principal
和
GrantedAuthority[]
。
Sid
接口提供實際安全對象(如
principal, role, group
等等)和
Acl
中實際內容的間接聯系,簡稱“安全身份”。普通的實現包括有
PrincipalSid
(在
Authentication
中表示一個
principal
)和
GrantedAuthoritySid
。安全身份信息放在
ACL_SID表中。
·????????
ObjectIdentity
:
ACL
模塊中,內部用
ObjectIdentity
來表示每個域對象。缺省的實現是
ObjectIdentityImpl
。
·????????
AclService
:用于獲取
ObjectIdentity
適用的
Acl
。在包含的實現(
JdbcAclService
)中,獲取操作委派給
LookupStrategy
。
LookupStrategy
為獲取
ACL
信息提供高度優化的策略,使用批獲?。?/code>
BasicLookupStrategy
),也支持用戶實現以提供更好的性能,例如層次化查詢和類似的以性能為中心的非
ANSI SQL
能力。
·????????
MutableAclService
:允許修改
Acl
以便進行持久化,如果不是為了這個可以不必使用這個接口。
請注意我們提供的AclService和相關的數據庫類都是使用ANSI SQL的。因此可以工作在所有主流數據庫服務器上。在編寫本文的時候,已經在Hypersonic SQL, PostgreSQL, Microsoft SQL Server 和 Oracle上測試過。
Spring Security發行包中有兩個例子可以用來演示ACL模塊。其一是Contacts例子,另外一個是Document Management System (DMS)。我們建議你仔細看看這些例子。
24.3.?入門
要開始使用Spring Security的ACL能力,你需要把你的ACL保存在某個地方。這要通過Spring實例化一個DataSource
。然后該數據源被注入到
JdbcMutableAclService
和
BasicLookupStrategy
。后者提供了高性能的
ACL
獲取能力,前者提供了可改變的能力。請參考發行包中任何一個例子來了解如何進行配置。你還需要往上一節中提到的四個表中填充數據(參考
ACL
例子看相關
SQL
語句)。
創建所需數據表并實例化JdbcMutableAclService
后,你還要確認你的領域模型支持與
Spring Security ACL包互操作。很可能ObjectIdentityImpl
已經提供了足夠的支持,它提供了多種可用的方式。大部分的域對象會包含一個
public Serializable
getId()
方法。如果返回類型是
long
,或者和
long
兼容(例如
int
),你就不需要煩
ObjectIdentity
的問題了。
ACL
模塊的很多部分都依賴于
long identifier
。如果沒有使用
long
(或
int
,
byte
等),你有機會要重新實現幾個類。我們不打算在
Spring Security ACL模塊中支持非
long identifier
,因為
long
和所有的數據庫
sequence
兼容,也是最為常用的
identifier
數據類型,同時有足夠的長度支持通常的應用場景。
下面的代碼展示如何創建一個Acl,或修改一個已存在的Acl:
// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;
// Create or update the relevant ACL
MutableAcl acl = null;
try {
?acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
?acl = aclService.createAcl(oi);
}
// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
在上面的例子中,我們獲取的ACL是和一個標識(identifer)為44的"Foo"域對象聯系在一起的。我們增加一個ACE以便一個叫"Samantha"的principal能"administer"該對象。除了insertAce方法,這段代碼不需要再多加說明。它的第一個參數是新的ACE要插入到Acl的什么位置。上面的例子中,我們把它放到最后。最后一個參數是一個表示該ACE授權或拒絕的boolean值。大部分時候應該為授權(true),但是如果為拒絕(false),該permission會被有效的關閉。
Spring Security沒有在DAO或repository中為自動創建、更新或刪除ACL提供任何特別集成。你要為域對象編寫像上面這樣的代碼。在你的業務層中使用AOP來自動集成ACL信息和你業務層操作是值得考慮的。我們發現這是一種很有效的方式。
一旦你已經使用了上面的技術來在數據庫中存儲一些 ACL 信息,下一步就是實際如何使用這些 ACL 信息作為你的授權決議邏輯的一部分。在這里你有好幾個選擇。你可以分別在方法調用前和調用后編寫自己的
AccessDecisionVoter
或
AfterInvocationProvider
。這些類應該使用
AclService
來獲取相關
ACL
,然后調用
Acl.isGranted(Permission[]
permission, Sid[] sids, boolean administrativeMode)
來決定是授權還是拒絕。你也可以使用我們的
AclEntryVoter
,
AclEntryAfterInvocationProvider
或
AclEntryAfterInvocationCollectionFilteringProvider
類。所有這些類都提供了基于聲明的方式,運行時根據這些設定信息來進行評估,把你從編寫代碼中解放出來。請參考例子應用學習如何使用這些類。