天天看點

Spring Security(19)——對Acl的支援

目錄

<a>1.1           準備工作</a>

<a>1.2           表功能介紹</a>

<a>1.2.1     表acl_sid</a>

<a>1.2.2     表acl_class</a>

<a>1.2.3     表acl_object_identity</a>

<a>1.2.4     表acl_entry</a>

<a>1.3           Acl主要接口</a>

<a>1.4           配置AclService</a>

<a>1.4.1     配置DataSource</a>

<a>1.4.2     配置LookupStrategy</a>

<a>1.4.3     配置AclAuthorizationStrategy</a>

<a>1.4.4     配置grantingStrategy</a>

<a>1.4.5     配置AclCache</a>

<a>1.5           使用AclService</a>

<a>1.5.1     建立Acl</a>

<a>1.5.2     查找Acl</a>

<a>1.5.3     更新Acl</a>

<a>1.5.4     删除Acl</a>

<a>1.6           注入到AclPermissionEvaluator</a>

       Acl的全稱是Access Control List,俗稱通路控制清單,是用以控制對象的通路權限的。其主要思想是将某個對象的某種權限授予給某個使用者,或某種GrantedAuthority(可以簡單的了解為某種角色),它們之間的關系都是多對多。如果某一個對象的某一操作是受保護的,那麼在對該對象進行某種操作時就需要有對應的權限。

       使用Spring Security的Acl功能需要引入Acl相關的jar包。如果我們的應用是使用Maven建構的,則可以在應用的pom.xml檔案中加入如下依賴。

   &lt;dependency&gt;

      &lt;groupId&gt;org.springframework.security&lt;/groupId&gt;

      &lt;artifactId&gt;spring-security-acl&lt;/artifactId&gt;

      &lt;version&gt;${spring.security.version}&lt;/version&gt;

   &lt;/dependency&gt;

       此外,使用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 (

  class varchar_ignorecase(100) not null,

  constraint unique_uk_2 unique(class) );

create table acl_object_identity (

  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 (

  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,

create table acl_class (

  class varchar(100) not null,

create table acl_object_identity (

  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 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 (

  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 foreign_fk_4 foreign key(acl_object_identity)

  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; 

       如上所示,Spring Security的Acl功能需要使用到四張資料庫表,分别為acl_sid、acl_class、acl_object_identity和acl_entry。

       表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的。

       表acl_class的結構如下所示:

class

對象類型的全限定名

       表acl_class是用來儲存對象類型的,字段class中儲存的是對應對象的全限定名。Acl需要使用它來區分不同的對象類型。

       表acl_object_identity的結構如下:

描述

object_id_class

關聯acl_class,表示對象類型

object_id_identity

對象的主鍵,對于相同的class而言,其需要是唯一的。對象的主鍵預設需要是Long型,或者可以轉換為Long型的對象,如Integer、Short等。

parent_object

父對象的id,關聯acl_object_identity

owner_sid

擁有者的sid,關聯acl_sid

entries_inheriting

是否繼承父對象的權限。打個比方,删除對象childObj需要有delete權限,使用者A他沒有childObj的delete權限,但是他有childObj的父對象parentObj的delete權限,當entries_inheriting為true時,使用者A同樣可以删除childObj。

       表acl_object_identity是用來存放需要進行通路控制的對象的資訊的。其儲存的資訊有對象的擁有者、對象的類型、對象的主鍵、對象的父對象和是否繼承父對象的權限。

       表acl_entry的結構如下:

acl_object_identity

對應acl_object_identity的id

ace_order

所屬Acl的權限順序

對應acl_sid的id

mask

權限對應的掩碼

granting

是否授權

audit_success

暫未發現其作用,Acl中有一個更新其值的方法,但未見被調用。

audit_failure

       表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的簡稱。

       對于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資訊的儲存的話,我們也可以不使用該接口。

       AclService是使用Spring Security Acl功能的主入口。這裡選擇一個既可以從資料庫讀取Acl資訊,又可以儲存Acl資訊到資料庫的JdbcMutableAclService做示例。

       JdbcMutableAclService隻有一個構造方法,它接收三個參數,DataSource、LookupStrategy和AclCache。其對應配置資訊如下所示:

   &lt;bean id="aclService"

      class="org.springframework.security.acls.jdbc.JdbcMutableAclService"&gt;

      &lt;constructor-arg ref="dataSource" /&gt;

      &lt;constructor-arg ref="lookupStrategy" /&gt;

      &lt;constructor-arg ref="aclCache" /&gt;

   &lt;/bean&gt;

       配置JdbcMutableAclService有一點需要注意的地方,那就是其在與資料庫進行互動的時候所基于的腳本是本文開始部分我們提到的那些腳本。其對應的資料庫表的主鍵是自增的,是以在儲存Acl時所給出的腳本中沒有新增主鍵id。比如在新增sid時預設使用的腳本是“insert into acl_sid (principal, sid) values (?, ?)”,顯然對于使用Oracle資料庫作為示例的我們來說這條SQL是有問題的,因為新增的時候主鍵不能為空,是以如果我們需要使用JdbcMutableAclService來建立Acl的話我們得給JdbcMutableAclService指定新增記錄時使用的腳本。這裡我們将在新增的時候從之前建立好的Sequence擷取值作為主鍵,示例如下:

      &lt;!-- 指定新增acl_sid的腳本 --&gt;

      &lt;property name="insertSidSql"

         value="insert into acl_sid(id, principal, sid) values (seq_acl_sid.nextval, ?, ?)" /&gt;

      &lt;!-- 指定新增acl_class的腳本 --&gt;

      &lt;property name="insertClassSql"

         value="insert into acl_class(id, class) values (seq_acl_class.nextval, ?)" /&gt;

      &lt;!-- 指定新增acl_object_identity的腳本 --&gt;

      &lt;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, ?, ?, ?, ?)" /&gt;

      &lt;!-- 指定新增acl_entry的腳本 --&gt;

      &lt;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, ?, ?, ?, ?, ?, ?, ?)" /&gt;

       除了上述四個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的目前值來擷取剛剛新增的記錄的主鍵。如:

      &lt;!-- 查詢剛剛新增的acl_sid的主鍵的SQL --&gt;

      &lt;property name="sidIdentityQuery" value="select seq_acl_sid.currval from dual" /&gt;

      &lt;!-- 查詢剛剛新增的acl_class的主鍵的SQL --&gt;

      &lt;property name="classIdentityQuery" value="select seq_acl_class.currval from dual" /&gt;

       配置資料源這個沒什麼好說的,大家都見慣了,為保持文章的完整性,我這裡還是把它列一下。直接上代碼:

   &lt;context:property-placeholder location="/WEB-INF/config/jdbc.properties" /&gt;

   &lt;bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"

      destroy-method="close"&gt;

      &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;

      &lt;property name="url" value="${jdbc.url}" /&gt;

      &lt;property name="username" value="${jdbc.username}" /&gt;

      &lt;property name="password" value="${jdbc.password}" /&gt;

       LookupStrategy是用來通過ObjectIdentity解析為對應的Acl的。Spring Security Acl中的預設實作類是BasicLookupStrategy,其的構造需要接收四個參數。

   &lt;bean id="lookupStrategy"

      class="org.springframework.security.acls.jdbc.BasicLookupStrategy"&gt;

      &lt;constructor-arg ref="aclAuthorizationStrategy" /&gt;

      &lt;constructor-arg ref="grantingStrategy" /&gt;

       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。

   &lt;bean id="aclAuthorizationStrategy"

      class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl"&gt;

      &lt;constructor-arg&gt;

         &lt;list&gt;

            &lt;bean class="org.springframework.security.core.authority.SimpleGrantedAuthority"&gt;

                &lt;constructor-arg value="ROLE_ADMIN" /&gt;&lt;!-- 改變所有權需要的權限 --&gt;

            &lt;/bean&gt;

                &lt;constructor-arg value="gaModifyAuditing" /&gt;&lt;!-- 改變授權需要的權限 --&gt;

                &lt;constructor-arg value="gaGeneralChanges" /&gt;&lt;!-- 改變其它資訊所需要的權限 --&gt;

         &lt;/list&gt;

      &lt;/constructor-arg&gt;

       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進行比對,并依次進行,如果都沒有比對到,則将抛出異常。

   &lt;bean id="grantingStrategy"

   class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy"&gt;

         &lt;bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" /&gt;

       AclCache是用來緩存Acl資訊的,Spring Security Acl中對于AclCache的預設實作是基于Ehcache的實作類EhCacheBasedAclCache。

   &lt;bean id="aclCache"

      class="org.springframework.security.acls.domain.EhCacheBasedAclCache"&gt;

      &lt;constructor-arg ref="cache" /&gt;&lt;!-- 對應于Ehcache --&gt;

   &lt;!-- 定義一個Ehcache --&gt;

   &lt;bean id="cache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"&gt;

      &lt;property name="cacheName" value="aclCache" /&gt;

      &lt;property name="cacheManager" ref="aclCacheManager" /&gt;

   &lt;!-- 定義CacheManager --&gt;

   &lt;bean id="aclCacheManager"

      class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"&gt;

      &lt;!-- 指定配置檔案的位置 --&gt;

      &lt;property name="configLocation" value="/WEB-INF/config/ehcache.xml" /&gt;

      &lt;!-- 指定建立的CacheManager的名稱 --&gt;

      &lt;property name="cacheManagerName" value="aclCacheManager" /&gt;

       為保持本文的完整性,這裡貼出上述使用到的配置檔案ehcache.xml的内容。具體如下所示:

&lt;ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"

   maxBytesLocalHeap="100M"&gt;

   &lt;diskStore path="d:\\ehcache" /&gt;

   &lt;defaultCache maxEntriesLocalHeap="200" /&gt;

   &lt;cache name="aclCache" maxBytesLocalHeap="50M" maxBytesLocalDisk="5G"

      timeToIdleSeconds="120" timeToLiveSeconds="600" /&gt;

&lt;/ehcache&gt;

       關于Ehcache的更多内容不在本文讨論範圍之内,有需要的讀者可以參考官方文檔,也可以參考我的另一個關于Ehcache的系列文章。

       至此,關于AclService配置的内容就講完了,AclService配置的完整内容如下:

       配置好AclService之後我們就可以使用該JdbcMutableAclService來建立、更新和查找Acl了。在Spring Security Acl中Acl接口的預設實作類是AclImpl,該類實作Acl接口、MutableAcl接口、AuditableAcl接口和OwnershipAcl接口,當有必要在這幾種接口之間切換時可以任意切換。

       可以通過調用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文檔。

       通過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);

       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());

    }

       添加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。如:

      //基于GrantedAuthority的Sid

      Sid sid = new GrantedAuthoritySid("ROLE_ADMIN");

       通過調用MutableAcl的deleteAce(int aceIndex)方法可以删除Acl中指定位置的AccessControlEntry,aceIndex是從0開始的,底層是使用的List的remove(int index)方法。

   acl.deleteAce(0);//記憶體中删除第一個AccessControlEntry

   aclService.updateAcl(acl);//同步到資料庫

       通過MutableAcl的updateAce()可以更新指定位置的AccessControlEntry的Permission。

   acl.updateAce(0, BasePermission.CREATE);//記憶體中更新第一個AccessControlEntry對應的Permission

       可以通過Acl的getOwner()方法擷取Acl對應ObjectIdentity的擁有者Sid。通過MutableAcl的setOwner()方法可以在記憶體中更新對應Acl的擁有者。

   acl.setOwner(new PrincipalSid("user"));//記憶體中更改擁有者

       通過Acl的getParentAcl()方法可以擷取到Acl對應ObjectIdentity對應的父ObjectIdentity對應的Acl。通過MutableAcl的setParent()方法可以在記憶體中修改Acl對應的父Acl,即修改Acl對應ObjectIdentity對應的父ObjectIdentity。

   Acl newParent = ...;//以某種方式擷取到Acl

   acl.setParent(newParent);//記憶體中更改父Acl

       通過Acl的isEntriesInheriting()可以擷取到目前Acl對應ObjectIdentity的繼承政策,建立Acl時該值預設為true。通過MutableAcl的setEntriesInheriting()方法可以在記憶體中修改該Acl對應ObjectIdentity的繼承政策。

   acl.setEntriesInheriting(false);//記憶體中修改為不從父Acl繼承AccessControlEntry

       當我們删除對象的時候應該連同對應的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);

       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。

   &lt;security:global-method-security pre-post-annotations="enabled"&gt;

      &lt;security:expression-handler ref="expressionHandler"/&gt;

   &lt;/security:global-method-security&gt;

   &lt;bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"&gt;

      &lt;property name="permissionEvaluator" ref="aclPermissionEvaluator"/&gt;

   &lt;bean id="aclPermissionEvaluator"

      class="org.springframework.security.acls.AclPermissionEvaluator"&gt;

      &lt;constructor-arg ref="aclService" /&gt;

       定義後之後我們就可以在方法上使用@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)")

       當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中。

      &lt;!-- 手工注冊DefaultPermissionFactory和其中的Permission --&gt;

      &lt;property name="permissionFactory"&gt;

         &lt;bean class="org.springframework.security.acls.domain.DefaultPermissionFactory"&gt;

            &lt;constructor-arg&gt;

                &lt;map&gt;

                   &lt;entry key="READ"&gt;

                      &lt;bean class="com.xxx.spring.security.BasePermissionExt"&gt;

                         &lt;constructor-arg value="1"/&gt;

                      &lt;/bean&gt;

                   &lt;/entry&gt;

                   &lt;entry key="WRITE"&gt;

                         &lt;constructor-arg value="2"/&gt;

                &lt;/map&gt;

            &lt;/constructor-arg&gt;

         &lt;/bean&gt;

      &lt;/property&gt;

       此外,還可以将我們的BasePermissionExt改成如下這樣:

   private static final longserialVersionUID = 1L;

    public static final Permission READ = new BasePermissionExt(1 &lt;&lt; 0, 'R'); // 1

    public static final Permission WRITE = new BasePermissionExt(1 &lt;&lt; 1, 'W'); // 2

    public static final Permission CREATE = new BasePermissionExt(1 &lt;&lt; 2, 'C'); // 4

    public static final Permission DELETE = new BasePermissionExt(1 &lt;&lt; 3, 'D'); // 8

    public static final Permission ADMINISTRATION = new BasePermissionExt(1 &lt;&lt; 4, 'A'); // 16

       然後通過傳入Class參數來構造DefaultPermissionFactory。這個時候會将對應Class中所有類型為Permission的字段分别以字段名和字段值Permission對應的掩碼為Key,以字段值Permission為Value進行注冊。

      &lt;!-- 手工注冊DefaultPermissionFactory和其中的Permission --&gt;

            &lt;constructor-arg value="com.spring.security.BasePermissionExt"/&gt;

      &lt;/property&gt;

(注:本文是基于Spring Security3.1.6所寫)