對Acl的支持
目錄
1.1 准備工作
1.2 表功能介紹
1.2.1 表acl_sid
1.2.2 表acl_class
1.2.3 表acl_object_identity
1.2.4 表acl_entry
1.3 Acl主要接口
1.4 配置AclService
1.4.1 配置DataSource
1.4.2 配置LookupStrategy
1.4.3 配置AclAuthorizationStrategy
1.4.4 配置grantingStrategy
1.4.5 配置AclCache
1.5 使用AclService
1.5.1 創建Acl
1.5.2 查找Acl
1.5.3 更新Acl
1.5.4 刪除Acl
1.6 注入到AclPermissionEvaluator
Acl的全稱是Access Control List,俗稱訪問控制列表,是用以控制對象的訪問權限的。其主要思想是將某個對象的某種權限授予給某個用戶,或某種GrantedAuthority(可以簡單的理解為某種角色),它們之間的關系都是多對多。如果某一個對象的某一操作是受保護的,那么在對該對象進行某種操作時就需要有對應的權限。
1.1 准備工作
使用Spring Security的Acl功能需要引入Acl相關的jar包。如果我們的應用是使用Maven構建的,則可以在應用的pom.xml文件中加入如下依賴。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>${spring.security.version}</version>
</dependency>
此外,使用Spring Security的Acl時需要在數據庫中建立四張表。在其官方文檔中給出了一個基於數據庫HSQLDB的建表語句。其腳本如下:
create table acl_sid (
id bigint generated by default as identity(start with 100) not null primary key,
principal boolean not null,
sid varchar_ignorecase(100) not null,
constraint unique_uk_1 unique(sid,principal) );
create table acl_class (
id bigint generated by default as identity(start with 100) not null primary key,
class varchar_ignorecase(100) not null,
constraint unique_uk_2 unique(class) );
create table acl_object_identity (
id bigint generated by default as identity(start with 100) not null primary key,
object_id_class bigint not null,
object_id_identity bigint not null,
parent_object bigint,
owner_sid bigint not null,
entries_inheriting boolean not null,
constraint unique_uk_3 unique(object_id_class,object_id_identity),
constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id),
constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id),
constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) );
create table acl_entry (
id bigint generated by default as identity(start with 100) not null primary key,
acl_object_identity bigint not null,ace_order int not null,sid bigint not null,
mask integer not null,granting boolean not null,audit_success boolean not null,
audit_failure boolean not null,
constraint unique_uk_4 unique(acl_object_identity,ace_order),
constraint foreign_fk_4 foreign key(acl_object_identity)
references acl_object_identity(id),
constraint foreign_fk_5 foreign key(sid) references acl_sid(id) );
筆者使用的是Oracle數據庫,其中沒有boolean和主鍵自增功能,對於boolean類型都使用一位number表示。具體建表語句如下所示:
create table acl_sid (
id number not null primary key,
principal number(1) not null,
sid varchar(100) not null,
constraint unique_uk_1 unique(sid,principal) );
create table acl_class (
id number not null primary key,
class varchar(100) not null,
constraint unique_uk_2 unique(class) );
create table acl_object_identity (
id number not null primary key,
object_id_class number not null,
object_id_identity number not null,
parent_object number,
owner_sid number not null,
entries_inheriting number(1) not null,
constraint unique_uk_3 unique(object_id_class,object_id_identity),
constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id),
constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id),
constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) );
create table acl_entry (
id number not null primary key,
acl_object_identity number not null,ace_order int not null,sid number not null,
mask number(3) not null,granting number(1) not null,audit_success number(1) not null,
audit_failure number(1) not null,
constraint unique_uk_4 unique(acl_object_identity,ace_order),
constraint foreign_fk_4 foreign key(acl_object_identity)
references acl_object_identity(id),
constraint foreign_fk_5 foreign key(sid) references acl_sid(id) );
新增記錄時用於生成主鍵的sequence定義為:
create or replace sequence seq_acl_sid start with 1 increment by 1;
create or replace sequence seq_acl_class start with 1 increment by 1;
create or replace sequence seq_acl_object_identity start with 1 increment by 1;
create or replace sequence seq_acl_entry start with 1 increment by 1;
1.2 表功能介紹
如上所示,Spring Security的Acl功能需要使用到四張數據庫表,分別為acl_sid、acl_class、acl_object_identity和acl_entry。
1.2.1表acl_sid
表acl_sid的結構如下所示:
字段名 |
類型 |
說明 |
id |
number |
主鍵 |
sid |
varchar |
字符串類型的sid |
principal |
boolean |
是否用戶 |
表acl_sid是用來保存Sid的。對於Acl而言,有兩種類型的Sid,一種是基於用戶的Sid,叫PrincipalSid;另一種是基於GrantedAuthority的Sid,叫GrantedAuthoritySid。acl_sid表的sid字段存放的是用戶名或者是GrantedAuthority的字符串表示。prinpal是用來區分對應的Sid是用戶還是GrantedAuthority的。正如在前文所描述的那樣,Acl中對象的權限是用來授予給Sid的,Sid有用戶和GrantedAuthority之分,所以我們的對象權限是可以用來授予給用戶或GrantedAuthority的。
1.2.2表acl_class
表acl_class的結構如下所示:
字段名 |
類型 |
說明 |
id |
number |
主鍵 |
class |
varchar |
對象類型的全限定名 |
表acl_class是用來保存對象類型的,字段class中保存的是對應對象的全限定名。Acl需要使用它來區分不同的對象類型。
1.2.3表acl_object_identity
表acl_object_identity的結構如下:
字段名 |
類型 |
描述 |
id |
number |
主鍵 |
object_id_class |
number |
關聯acl_class,表示對象類型 |
object_id_identity |
number |
對象的主鍵,對於相同的class而言,其需要是唯一的。對象的主鍵默認需要是Long型,或者可以轉換為Long型的對象,如Integer、Short等。 |
parent_object |
number |
父對象的id,關聯acl_object_identity |
owner_sid |
number |
擁有者的sid,關聯acl_sid |
entries_inheriting |
boolean |
是否繼承父對象的權限。打個比方,刪除對象childObj需要有delete權限,用戶A他沒有childObj的delete權限,但是他有childObj的父對象parentObj的delete權限,當entries_inheriting為true時,用戶A同樣可以刪除childObj。 |
表acl_object_identity是用來存放需要進行訪問控制的對象的信息的。其保存的信息有對象的擁有者、對象的類型、對象的主鍵、對象的父對象和是否繼承父對象的權限。
1.2.4表acl_entry
表acl_entry的結構如下:
字段名 |
類型 |
說明 |
id |
number |
主鍵 |
acl_object_identity |
number |
對應acl_object_identity的id |
ace_order |
number |
所屬Acl的權限順序 |
sid |
number |
對應acl_sid的id |
mask |
number |
權限對應的掩碼 |
granting |
boolean |
是否授權 |
audit_success |
boolean |
暫未發現其作用,Acl中有一個更新其值的方法,但未見被調用。 |
audit_failure |
boolean |
表acl_entry是用於存放具體的權限信息的,從表結構我們也可以看出來,其描述的就是某個主體(Sid)對某個對象(acl_object_identity)是否(granting)擁有某種權限(mask)。當同一對象acl_object_identity在acl_entry表中擁有多條記錄時,就會使用ace_order來標記對應的順序,其對應於往Acl中插入AccessControlEntry時的位置,在進行權限判斷時也是依靠ace_order的順序來進行的,ace_order越小的越先進行判斷。ace是Access Control Entry的簡稱。
1.3 Acl主要接口
對於Acl而言,有兩塊比較核心的功能,一塊是往對應的數據庫表里面插數據,另一塊是從數據庫表里面取出對應的數據進行權限鑒定。要了解這些功能我們先來了解Acl中用到的主要接口。
l Sid:可以用來表示一個principal,或者是一個GrantedAuthority。其對應的實現類有表示principal的PrincipalSid和表示GrantedAuthority的GrantedAuthoritySid。其信息會保存在acl_sid表中。
l ObjectIdentity:ObjectIdentity表示Spring Security Acl中一個域對象,其默認實現類是ObjectIdentityImpl。ObjectIdentity並不是直接與acl_object_identity表相對應的,真正與acl_object_identity表直接相對應的是Acl。
l Acl:每一個領域對象都會對應一個Acl,而且只會對應一個Acl。Acl是將Spring Security Acl中使用到的四個表串聯起來的一個接口,其中會包含對象信息ObjectIdentity、對象的擁有者Sid和對象的訪問控制信息AccessControlEntry。在Spring Security Acl中直接與acl_object_identity表相關聯的是Acl接口,因為acl_object_identity表中的數據是通過保存Acl來進行的。一個Acl對應於一個ObjectIdentity,但是會包含有多個Sid和多個AccessControlEntry,即一個Acl表示所有Sid對一個ObjectIdentity的所有AccessControlEntry。Acl的默認實現類是AclImpl,該類實現Acl接口、MutableAcl接口、AuditableAcl接口和OwnershipAcl接口。
l AccessControlEntry:一個AccessControlEntry表示一條訪問控制信息,一個Acl中可以擁有多個AccessControlEntry。在Spring Security Acl中很多地方會使用ACE來簡單的表示AccessControlEntry這個概念,比如insertAce其實表示的就是insert AccessControlEntry。每一個AccessControlEntry表示對應的Sid對於對應的對象ObjectIdentity是否被授權某一項權限Permission,是否被授權將使用granting進行區分。AccessControlEntry對應表acl_entry。
l Permission:在Acl中使用一個bit掩碼來表示一個Permission。Spring Security的Acl中默認使用的是BasePermission,其中已經定義了0-4五個bit掩碼,分別對應於1、2、4、8、16,代表五種不同的Permission,分別是read (bit 0)、write (bit 1)、create (bit 2)、delete (bit 3)和administer (bit 4)。如果已經定義好的這五個bit掩碼不能滿足需求,我們可以對BasePermission進行擴展,也可以實現自己的Permission。Spring Security Acl默認的實現最多可以支持32個不同的掩碼。
l AclService:AclService是用來通過ObjectIdentity解析Acl的,其默認實現類是JdbcAclService。JdbcAclService底層操作是通過LookupStrategy來進行的,LookupStrategy的默認實現是BasicLookupStrategy。
l MutableAclService:MutableAclService是用來對Acl進行持久化的,其默認實現類是JdbcMutableAclService。JdbcMutableAclService是繼承自JdbcAclService的,所以我們可以同時通過JdbcMutableAclService對Acl進行讀取和保存。如果我們希望自己來實現Acl信息的保存的話,我們也可以不使用該接口。
1.4 配置AclService
AclService是使用Spring Security Acl功能的主入口。這里選擇一個既可以從數據庫讀取Acl信息,又可以保存Acl信息到數據庫的JdbcMutableAclService做示例。
JdbcMutableAclService只有一個構造方法,它接收三個參數,DataSource、LookupStrategy和AclCache。其對應配置信息如下所示:
<bean id="aclService"
class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
</bean>
配置JdbcMutableAclService有一點需要注意的地方,那就是其在與數據庫進行交互的時候所基於的腳本是本文開始部分我們提到的那些腳本。其對應的數據庫表的主鍵是自增的,所以在保存Acl時所給出的腳本中沒有新增主鍵id。比如在新增sid時默認使用的腳本是“insert into acl_sid (principal, sid) values (?, ?)”,顯然對於使用Oracle數據庫作為示例的我們來說這條SQL是有問題的,因為新增的時候主鍵不能為空,所以如果我們需要使用JdbcMutableAclService來創建Acl的話我們得給JdbcMutableAclService指定新增記錄時使用的腳本。這里我們將在新增的時候從之前建立好的Sequence獲取值作為主鍵,示例如下:
<bean id="aclService"
class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
<!-- 指定新增acl_sid的腳本 -->
<property name="insertSidSql"
value="insert into acl_sid(id, principal, sid) values (seq_acl_sid.nextval, ?, ?)" />
<!-- 指定新增acl_class的腳本 -->
<property name="insertClassSql"
value="insert into acl_class(id, class) values (seq_acl_class.nextval, ?)" />
<!-- 指定新增acl_object_identity的腳本 -->
<property name="insertObjectIdentitySql"
value="insert into acl_object_identity(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) values(seq_acl_object_identity.nextval, ?, ?, ?, ?)" />
<!-- 指定新增acl_entry的腳本 -->
<property name="insertEntrySql"
value="insert into acl_entry(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) values (seq_acl_entry.nextval, ?, ?, ?, ?, ?, ?, ?)" />
</bean>
除了上述四個SQL之外,我們還需要指定兩個屬性對應的查詢SQL,sidIdentityQuery和classIdentityQuery。因為JdbcMutableAclService在創建Acl時,如果當前用戶在acl_sid表中不存在或當前對象類型在acl_class表中不存在,其會先將對應的信息存入acl_sid表和acl_class表,然后需要取出剛剛新增的acl_sid的主鍵和acl_class的主鍵以往acl_object_identity表中插入數據,對應acl_object_identity表中的owner_sid和object_id_class字段。這兩個屬性的默認值是“call identity()”,顯然對於Oracle數據庫來說這是行不通的,所以我們需要自己指定它們。這里我們通過對應Sequence的當前值來獲取剛剛新增的記錄的主鍵。如:
<bean id="aclService"
class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
<!-- 指定新增acl_sid的腳本 -->
<property name="insertSidSql"
value="insert into acl_sid(id, principal, sid) values (seq_acl_sid.nextval, ?, ?)" />
<!-- 指定新增acl_class的腳本 -->
<property name="insertClassSql"
value="insert into acl_class(id, class) values (seq_acl_class.nextval, ?)" />
<!-- 指定新增acl_object_identity的腳本 -->
<property name="insertObjectIdentitySql"
value="insert into acl_object_identity(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) values(seq_acl_object_identity.nextval, ?, ?, ?, ?)" />
<!-- 指定新增acl_entry的腳本 -->
<property name="insertEntrySql"
value="insert into acl_entry(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) values (seq_acl_entry.nextval, ?, ?, ?, ?, ?, ?, ?)" />
<!-- 查詢剛剛新增的acl_sid的主鍵的SQL -->
<property name="sidIdentityQuery" value="select seq_acl_sid.currval from dual" />
<!-- 查詢剛剛新增的acl_class的主鍵的SQL -->
<property name="classIdentityQuery" value="select seq_acl_class.currval from dual"/>
</bean>
1.4.1配置DataSource
配置數據源這個沒什么好說的,大家都見慣了,為保持文章的完整性,我這里還是把它列一下。直接上代碼:
<context:property-placeholder location="/WEB-INF/config/jdbc.properties" />
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
1.4.2配置LookupStrategy
LookupStrategy是用來通過ObjectIdentity解析為對應的Acl的。Spring Security Acl中的默認實現類是BasicLookupStrategy,其的構造需要接收四個參數。
<bean id="lookupStrategy"
class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="grantingStrategy" />
</bean>
1.4.3配置AclAuthorizationStrategy
aclAuthorizationStrategy是在構造Acl的實現類AclImpl時必須給定的一個參數,其會用來在對Acl進行某些操作時檢查當前用戶是否具有對應的權限。AclAuthorizationStrategy的默認實現類是AclAuthorizationStrategyImpl,其構造需要接收一個或三個GrantedAuthority參數,用來對Acl進行相關操作時所需要的權限,包括更改Acl對應對象的所有者需要的權限、更改Acl中包含的某個AccessControlEntry的audit信息(對應acl_entry表中的is_audit_success和is_audit_failure字段)需要的權限以及其它如增、刪、改Acl中所包含的AccessControlEntry等需要的權限。這些權限的鑒定是我們在操作Acl時由Spring Security Acl內部進行判斷的,我們只需要在這里定義就好。當Acl對應的所有者對Acl進行操作時,不管其是否擁有指定需要的權限,除了改變audit信息之外的所有操作默認都是被允許的。當只有一個參數時表示三者共用一個GrantedAuthority。
<bean id="aclAuthorizationStrategy"
class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN" /><!-- 改變所有權需要的權限 -->
</bean>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="gaModifyAuditing" /><!-- 改變授權需要的權限 -->
</bean>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="gaGeneralChanges" /><!-- 改變其它信息所需要的權限 -->
</bean>
</list>
</constructor-arg>
</bean>
1.4.4配置grantingStrategy
grantingStrategy對應類型為PermissionGrantingStrategy接口,其中只定義了一個isGranted方法,用於判斷基於指定的Permission列表和Sid列表指定的Acl是否被授予了訪問權限。其默認實現類是DefaultPermissionGrantingStrategy。DefaultPermissionGrantingStrategy對於isGranted的實現邏輯是依次遍歷Permission列表、Sid列表和Acl中包含的AccessControlEntry列表,找到第一個三者能夠匹配的AccessControlEntry的isGranting(對應acl_entry表的granting字段)作為isGranted的返回結果。如果在當前Acl中沒有找到匹配的AccessControlEntry,同時Acl對應的entriesInheriting為true時將繼續使用父級的Acl進行匹配,並依次進行,如果都沒有匹配到,則將拋出異常。
<bean id="grantingStrategy"
class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
1.4.5配置AclCache
AclCache是用來緩存Acl信息的,Spring Security Acl中對於AclCache的默認實現是基於Ehcache的實現類EhCacheBasedAclCache。
<bean id="aclCache"
class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg ref="cache" /><!-- 對應於Ehcache -->
<constructor-arg ref="grantingStrategy" />
<constructor-arg ref="aclAuthorizationStrategy" />
</bean>
<!-- 定義一個Ehcache -->
<bean id="cache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="aclCache" />
<property name="cacheManager" ref="aclCacheManager" />
</bean>
<!-- 定義CacheManager -->
<bean id="aclCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml" />
<!-- 指定新建的CacheManager的名稱 -->
<property name="cacheManagerName" value="aclCacheManager" />
</bean>
為保持本文的完整性,這里貼出上述使用到的配置文件ehcache.xml的內容。具體如下所示:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
maxBytesLocalHeap="100M">
<diskStore path="d:\\ehcache" />
<defaultCache maxEntriesLocalHeap="200" />
<cache name="aclCache" maxBytesLocalHeap="50M" maxBytesLocalDisk="5G"
timeToIdleSeconds="120" timeToLiveSeconds="600" />
</ehcache>
關於Ehcache的更多內容不在本文討論范圍之內,有需要的讀者可以參考官方文檔,也可以參考我的另一個關於Ehcache的系列文章。
至此,關於AclService配置的內容就講完了,AclService配置的完整內容如下:
<bean id="aclService"
class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
<!-- 指定新增acl_sid的腳本 -->
<property name="insertSidSql"
value="insert into acl_sid(id, principal, sid) values (seq_acl_sid.nextval, ?, ?)" />
<!-- 指定新增acl_class的腳本 -->
<property name="insertClassSql"
value="insert into acl_class(id, class) values (seq_acl_class.nextval, ?)" />
<!-- 指定新增acl_object_identity的腳本 -->
<property name="insertObjectIdentitySql"
value="insert into acl_object_identity(id, object_id_class, object_id_identity, owner_sid, entries_inheriting) values(seq_acl_object_identity.nextval, ?, ?, ?, ?)" />
<!-- 指定新增acl_entry的腳本 -->
<property name="insertEntrySql"
value="insert into acl_entry(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) values (seq_acl_entry.nextval, ?, ?, ?, ?, ?, ?, ?)" />
<!-- 查詢剛剛新增的acl_sid的主鍵的SQL -->
<property name="sidIdentityQuery" value="select seq_acl_sid.currval from dual" />
<!-- 查詢剛剛新增的acl_class的主鍵的SQL -->
<property name="classIdentityQuery" value="select seq_acl_class.currval from dual"/>
</bean>
<bean id="lookupStrategy"
class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="grantingStrategy" />
</bean>
<bean id="aclCache"
class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg ref="cache" /><!-- 對應於Ehcache -->
<constructor-arg ref="grantingStrategy" />
<constructor-arg ref="aclAuthorizationStrategy" />
</bean>
<!-- 定義一個Ehcache -->
<bean id="cache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="aclCache" />
<property name="cacheManager" ref="aclCacheManager" />
</bean>
<!-- 定義CacheManager -->
<bean id="aclCacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml" />
<!-- 指定新建的CacheManager的名稱 -->
<property name="cacheManagerName" value="aclCacheManager" />
</bean>
<bean id="aclAuthorizationStrategy"
class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN" /><!-- 改變所有權需要的權限 -->
</bean>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="gaModifyAuditing" /><!-- 改變授權需要的權限 -->
</bean>
<beanclass="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="gaGeneralChanges" /><!-- 改變其它信息所需要的權限 -->
</bean>
</list>
</constructor-arg>
</bean>
<bean id="grantingStrategy"
class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
1.5 使用AclService
配置好AclService之后我們就可以使用該JdbcMutableAclService來創建、更新和查找Acl了。在Spring Security Acl中Acl接口的默認實現類是AclImpl,該類實現Acl接口、MutableAcl接口、AuditableAcl接口和OwnershipAcl接口,當有必要在這幾種接口之間切換時可以任意切換。
1.5.1創建Acl
可以通過調用JdbcMutableAclService的createAcl()方法來創建一個Acl,其對應返回的是一個MutableAcl,該方法接收一個ObjectIdentity作為參數。在創建的時候如果ObjectIdentity對應的類型在acl_class表中不存在,則會把ObjectIdentity對應的類型添加到acl_class表中;如果當前用戶對應的Sid在acl_sid表中不存在則會將其添加到acl_sid中。最后會將ObjectIdentity保存到acl_object_identity表中。正如在本文開始部分所描述的那樣,一個Acl對應於一個ObjectIdentity,創建Acl就是創建ObjectIdentity的過程。在這三部分都完成之后,會重新從數據庫查詢出一個Acl,只是此時該Acl對應的AccessControlEntry列表為空。通常如果我們的對象是需要利用Acl進行訪問控制的話,那么我們可以在創建該對象的時候一並創建該對象對應的Acl。
@Autowired
private MutableAclService aclService;
public void addUser(User user) {
...
//1、構建一個ObjectIdentity
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//2、創建一個Acl、此時會如果對應的信息不存在會依次創建,如當前用戶對應的Sid、ObjectIdentity對應於acl_class表中的類型
//最后是往acl_object_identity中插入對應的數據
MutableAcl acl = aclService.createAcl(oi);
...
}
ObjectIdentityImpl擁有多個構造方法,具體可以參考Spring Security的API文檔。
1.5.2查找Acl
通過AclService的系列readAclById()方法可以通過給定的ObjectIdentity查找對應的Acl。此外通過findChildren()方法可以查找指定ObjectIdentity的子ObjectIdentity。關於這些方法的具體信息可以參考Spring Security的API文檔。以下是一個簡單的示例。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
1.5.3更新Acl
Acl的更新主要是對應AccessControlEntry的更新,即對AccessControlEntry的增、刪、改;此外還包括對Acl對應的ObjectIdentity信息的變更,如更改所有者、父子關系等。
如下是一些更新Acl的示例,需要注意的是在調用MutableAclService的updateAcl()方法將對應信息同步到數據庫之前,對Acl所做的所有修改都只是在內存中的。使用updateAcl()更新Acl信息到數據庫時,其底層實現會先將數據庫中所有對應的AccessControlEntry都刪除,然后再將內存中的AccessControlEntry列表保存到數據庫中。以下是其實現代碼。
public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException {
Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
// Delete this ACL's ACEs in the acl_entry table
deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()));
// Create this ACL's ACEs in the acl_entry table
createEntries(acl);
// Change the mutable columns in acl_object_identity
updateObjectIdentity(acl);
// Clear the cache, including children
clearCacheIncludingChildren(acl.getObjectIdentity());
// Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
return (MutableAcl) super.readAclById(acl.getObjectIdentity());
}
1.5.3.1添加AccessControlEntry
添加AccessControlEntry是通過MutableAcl的insertAce()方法進行的,該方法的定義如下所示:
/**
*
* @param atIndexLocation 添加的位置,對應於acl_entry表中的ace_order字段
* @param permission 對應的Permission
* @param sid 對應Sid
* @param granting 是否賦予
*/
void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting)
參數atIndexLocation對應的是需要插入的AccessControlEntry在Acl對應的AccessControlEntry列表(java.util.List類型)中的位置,對應於acl_entry表中ace_order,而一個Acl代表其對應ObjectIdentity的所有關聯Sid關聯的所有AccessControlEntry,這個ace_order是在這個范圍內的order。以下是一個添加AccessControlEntry的示例:
MutableAcl acl = ...;
//基於principal的Sid
Sid sid = new PrincipalSid(SecurityContextHolder.getContext().getAuthentication());
Permission p = BasePermission.ADMINISTRATION;//管理員權限
//將當前Acl的管理員權限賦予給指定的Sid
acl.insertAce(acl.getEntries().size(), p, sid, true); //添加AccessControlEntry到內存
//保存到數據庫
acl = aclService.updateAcl(acl);
上述的Sid,也可以是一個GrantedAuthoritySid,當把一個Acl的某Permission賦予給一個GrantedAuthoritySid時表示擁有該GrantedAuthority的用戶都將擁有對應的Permission。如:
MutableAcl acl = ...;
//基於GrantedAuthority的Sid
Sid sid = new GrantedAuthoritySid("ROLE_ADMIN");
Permission p = BasePermission.ADMINISTRATION;//管理員權限
//將當前Acl的管理員權限賦予給指定的Sid
acl.insertAce(acl.getEntries().size(), p, sid, true); //添加AccessControlEntry到內存
//保存到數據庫
acl = aclService.updateAcl(acl);
1.5.3.2刪除AccessControlEntry
通過調用MutableAcl的deleteAce(int aceIndex)方法可以刪除Acl中指定位置的AccessControlEntry,aceIndex是從0開始的,底層是使用的List的remove(int index)方法。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
acl.deleteAce(0);//內存中刪除第一個AccessControlEntry
aclService.updateAcl(acl);//同步到數據庫
1.5.3.3更新AccessControlEntry
通過MutableAcl的updateAce()可以更新指定位置的AccessControlEntry的Permission。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
acl.updateAce(0, BasePermission.CREATE);//內存中更新第一個AccessControlEntry對應的Permission
aclService.updateAcl(acl);//同步到數據庫
1.5.3.4修改所有者
可以通過Acl的getOwner()方法獲取Acl對應ObjectIdentity的擁有者Sid。通過MutableAcl的setOwner()方法可以在內存中更新對應Acl的擁有者。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
acl.setOwner(new PrincipalSid("user"));//內存中更改擁有者
aclService.updateAcl(acl);//同步到數據庫
1.5.3.5修改父Acl
通過Acl的getParentAcl()方法可以獲取到Acl對應ObjectIdentity對應的父ObjectIdentity對應的Acl。通過MutableAcl的setParent()方法可以在內存中修改Acl對應的父Acl,即修改Acl對應ObjectIdentity對應的父ObjectIdentity。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
Acl newParent = ...;//以某種方式獲取到Acl
acl.setParent(newParent);//內存中更改父Acl
aclService.updateAcl(acl);//同步到數據庫
1.5.3.6修改繼承策略
通過Acl的isEntriesInheriting()可以獲取到當前Acl對應ObjectIdentity的繼承策略,創建Acl時該值默認為true。通過MutableAcl的setEntriesInheriting()方法可以在內存中修改該Acl對應ObjectIdentity的繼承策略。
ObjectIdentity oi = new ObjectIdentityImpl(User.class, user.getId());
//獲取ObjectIdentity對應的Acl
MutableAcl acl = (MutableAcl) aclService.readAclById(oi);
acl.setEntriesInheriting(false);//內存中修改為不從父Acl繼承AccessControlEntry
aclService.updateAcl(acl);//同步到數據庫
1.5.4刪除Acl
當我們刪除對象的時候應該連同對應的Acl也一起刪除。使用MutableAclService的deleteAcl(ObjectIdentity oi, boolean deleteChildren)方法可以刪除指定ObjectIdentity對應的Acl,deleteChildren表示是否連同子ObjectIdentity對應的Acl也一起刪除。deleteAcl將刪除對應的ObjectIdentity,以及對應的AccessControlEntry,即其會刪除acl_object_identity表和acl_entry表中與當前Acl對應的ObjectIdentity相關的記錄。
ObjectIdentity objectIdentity = new ObjectIdentityImpl(User.class, id);
aclService.deleteAcl(objectIdentity, true);
1.6 注入到AclPermissionEvaluator
AclPermissionEvaluator是PermissionEvaluator的一個實現類。在之前關於使用基於表達式的權限控制一文中有提到過,PermissionEvaluator是為表達式hasPermission提供支持的。此外,PermissionEvaluator還為標簽accesscontrollist提供支持。本節將就使用AclPermissionEvaluator支持在方法上使用@PreAuthorize進行權限控制時使用表達式hasPermission做一個簡單講解。
AclPermissionEvaluator的構造需要接收一個AclService參數,在進行權限鑒定時其需要通過AclService獲取到對應對象對應的Acl,然后判斷該Acl中是否具有指定的Sid和指定的Permission。
對於方法使用hasPermission表達式進行權限鑒定時需要做兩個事情,首先需要指定global-method-security的pre-post-annotations="enabled"。其次需要手工定義DefaultMethodSecurityExpressionHandler並指定其permissionEvaluator為我們定義的AclPermissionEvaluator。
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="expressionHandler"class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="aclPermissionEvaluator"/>
</bean>
<bean id="aclPermissionEvaluator"
class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
</bean>
定義后之后我們就可以在方法上使用@PreAuthorize和hasPermission表達式了。關於PermissionEvaluator和hasPermission的更多介紹可以參考《基於表達式的權限控制》一文。hasPermission有兩種用法,一種是直接傳一個對象和對應需要的權限,如hasPermission(object,permission);另一種是傳對象的id、對應類型和需要的權限,如hasPermission(targetId,targetType,permission)。傳入的Permission是Spring Security Acl中的Permission接口實現類,而不是Spring Security的GrantedAuthority。傳入的permission參數可以是一個Permission對象或Permission對象數組,也可以是一個整數或字符串。當傳入的permission是整數或字符串時將由AclPermissionEvaluator的PermissionFactory進行解析,AclPermissionEvaluator默認擁有的PermissionFactory是DefaultPermissionFactory,其會將整形或字符串類型的permission解析成對應的BasePermission。如前所述,BasePermission中定義了五個BasePermission,其對應的名稱和掩碼分別為:READ (1)、WRITE (2)、CREATE (4)、DELETE (8)和ADMINISTER (16)。當permission使用字符串時我們只能使用這五種字符串,不區分大小寫,表示當前用戶或其所擁有的GrantedAuthority必須擁有指定對象的指定Permission才允許訪問。但是當使用掩碼時我們可以使用1、2、4、8和16。
接下來將簡單的介紹一個使用@PreAuthorize和hasPermission表達式在方法上進行權限控制的示例。
@PreAuthorize("hasPermission(#id, 'com.spring.security.entity.User', 1)")
public User find(int id) {
User user = new User();
user.setId(id);
return user;
}
上述配置即表示調用find方法查看指定id對應的User對象時必須擁有掩碼為1對應的Permission才行,或者在繼續策略為true時擁有指定id父對象掩碼為1對應的Permission也行。這時哪怕你是該User對象的擁有者,或者你擁有ADMINISTER權限,如果你沒有對應的READ權限,你也不能訪問該方法。如果覺得這種實現不符合你的要求,你可以實現自己的PermissionGrantingStrategy,然后將實現類bean注入到BasicLookupStrategy和EhcacheBasedAclCache中,這樣在判斷一個用戶是否具有指定Acl的指定Permission時就可以使用自己的邏輯了。
上面的定義如果改成permission參數直接使用對象,可以這樣定義:
@PreAuthorize("hasPermission(#id, 'com.spring.security.entity.User', T(org.springframework.security.acls.domain.BasePermission).READ)")
public User find(int id) {
User user = new User();
user.setId(id);
return user;
}
當permission參數定義為一個Permission數組時,會根據順序依次匹配當前用戶在指定的Acl中是否擁有對應Permission的AccessControlEntry,如果擁有則以第一個匹配到的AccessControlEntry的granting屬性作為判斷結果,沒有匹配到還可以根據繼承策略決定是否利用父級Acl進行匹配,都沒匹配到就會拋異常了。
默認使用的BasePermission中只定義了五種Permission,如果這不能滿足你的要求,那么我們可以實現自己的Permission,然后把它們注冊到DefaultPermissionFactory中,並手工將該DefaultPermissionFactory注入到AclPermissionEvaluator中。前文已經說過AclPermissionEvaluator中使用的PermissionFactory默認是DefaultPermissionFactory,DefaultPermissionFactory中默認只注冊了BasePermission中對應的五種Permission。以下是一個擴展Permission的簡單示例。
首先實現自己的Permission類,這里簡單的定義一個自己的類,然后繼承BasePermission類。
public class BasePermissionExt extends BasePermission {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
public BasePermissionExt(int mask) {
super(mask);
}
public BasePermissionExt(int mask, char code) {
super(mask, code);
}
}
然后在DefaultPermissionFactory中注冊基於我們自己實現的Permission對象,並將該DefaultPermissionFactory注入到AclPermissionEvaluator中。
<bean id="aclPermissionEvaluator"
class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
<!-- 手工注冊DefaultPermissionFactory和其中的Permission -->
<property name="permissionFactory">
<bean class="org.springframework.security.acls.domain.DefaultPermissionFactory">
<constructor-arg>
<map>
<entry key="READ">
<bean class="com.xxx.spring.security.BasePermissionExt">
<constructor-arg value="1"/>
</bean>
</entry>
<entry key="WRITE">
<bean class="com.xxx.spring.security.BasePermissionExt">
<constructor-arg value="2"/>
</bean>
</entry>
</map>
</constructor-arg>
</bean>
</property>
</bean>
此外,還可以將我們的BasePermissionExt改成如下這樣:
public class BasePermissionExt extends BasePermission {
/**
* serialVersionUID
*/
private static final longserialVersionUID = 1L;
public static final Permission READ = new BasePermissionExt(1 << 0, 'R'); // 1
public static final Permission WRITE = new BasePermissionExt(1 << 1, 'W'); // 2
public static final Permission CREATE = new BasePermissionExt(1 << 2, 'C'); // 4
public static final Permission DELETE = new BasePermissionExt(1 << 3, 'D'); // 8
public static final Permission ADMINISTRATION = new BasePermissionExt(1 << 4, 'A'); // 16
public BasePermissionExt(int mask) {
super(mask);
}
public BasePermissionExt(int mask, char code) {
super(mask, code);
}
}
然后通過傳入Class參數來構造DefaultPermissionFactory。這個時候會將對應Class中所有類型為Permission的字段分別以字段名和字段值Permission對應的掩碼為Key,以字段值Permission為Value進行注冊。
<bean id="aclPermissionEvaluator"
class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
<!-- 手工注冊DefaultPermissionFactory和其中的Permission -->
<property name="permissionFactory">
<bean class="org.springframework.security.acls.domain.DefaultPermissionFactory">
<constructor-arg value="com.spring.security.BasePermissionExt"/>
</bean>
</property>
</bean>
(注:本文是基於Spring Security3.1.6所寫)
(注:原創文章,轉載請注明出處。原文地址:http://haohaoxuexi.iteye.com/blog/2269021)