[收藏]Spring Security中的ACL

IT一族發表於2014-07-15
ACL即訪問控制列表(Access Controller List),它是用來做細粒度許可權控制所用的一種許可權模型。對ACL最簡單的描述就是兩個業務員,每個人只能檢視操作自己籤的合同,而不能看到對方的合同資訊。
下面我們會介紹Spring Security中是如何實現ACL的。

23.1. 準備資料庫和aclService

ACL所需的四張表,表結構見附錄:附錄 E, 資料庫表結構
然後我們需要配置aclService,它負責與資料庫進行互動。

23.1.1. 為acl配置cache

預設使用ehcache,spring security提供了一些預設的實現類。
<bean id="aclCache" class="org.springframework.security.acls.jdbc.EhCacheBasedAclCache">
    <constructor-arg ref="aclEhCache"/>
</bean>
<bean id="aclEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="cacheName" value="aclCache"/>
</bean>
            
在ehcache.xml中配置對應的aclCache快取策略。
<cache
    name="aclCache"
    maxElementsInMemory="1000"
    eternal="false"
    timeToIdleSeconds="600"
    timeToLiveSeconds="3600"
    overflowToDisk="true"
/>
 <!--Default Cache configuration. These will applied to caches programmatically created through
         the CacheManager.

         The following attributes are required:

         maxElementsInMemory             - Sets the maximum number of objects that will be created in memory
         eternal                         - Sets whether elements are eternal. If eternal,   timeouts are ignored and the
                                          element is never expired.
         overflowToDisk                  - Sets whether elements can overflow to disk when the in-memory cache
                                          has reached the maxInMemory limit.

         The following attributes are optional:
         timeToIdleSeconds               - Sets the time to idle for an element before it expires.
                                          i.e. The maximum amount of time between accesses before an element expires
                                          Is only used if the element is not eternal.
                                          Optional attribute. A value of 0 means that an Element can idle for infinity.
                                          The default value is 0.
         timeToLiveSeconds               - Sets the time to live for an element before it expires.
                                          i.e. The maximum time between creation time and when an element expires.
                                          Is only used if the element is not eternal.
                                          Optional attribute. A value of 0 means that and Element can live for infinity.
                                          The default value is 0.
         diskPersistent                  - Whether the disk store persists between restarts of the Virtual Machine.
                                          The default value is false.
         diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
                                          is 120 seconds.
         -->

23.1.2. 配置lookupStrategy

簡單來說,lookupStrategy的作用就是從資料庫中讀取資訊,把這些資訊提供給aclService使用,所以我們要為它配置一個dataSource,配置中還可以看到一個aclCache,這就是上面我們配置的快取,它會把資源最大限度的利用起來。
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
    <constructor-arg ref="dataSource"/>
    <constructor-arg ref="aclCache"/>
    <constructor-arg>
        <bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
            <constructor-arg>
                <list>
                    <ref local="adminRole"/>
                    <ref local="adminRole"/>
                    <ref local="adminRole"/>
                </list>
            </constructor-arg>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
    </constructor-arg>
</bean>
<bean id="adminRole" class="org.springframework.security.GrantedAuthorityImpl">
    <constructor-arg value="ROLE_ADMIN"/>
</bean>
            
中間一部分可能會讓人感到困惑,為何一次定義了三個adminRole呢?這是因為一旦acl資訊被儲存到資料庫中,無論是修改它的從屬者,還是變更授 權,抑或是修改其他的ace資訊,都需要控制操作者的許可權,這裡配置的三個許可權將對應於上述的三種修改操作,我們把它配置成,只有ROLE_ADMIN才 能執行這三種修改操作。

23.1.3. 配置aclService

當我們已經擁有了dataSource, lookupStrategy和aclCache的時候,就可以用它們來組裝aclService了,之後所有的acl操作都是基於aclService展開的。
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
    <constructor-arg ref="dataSource"/>
    <constructor-arg ref="lookupStrategy"/>
    <constructor-arg ref="aclCache"/>
</bean>
            

23.2. 使用aclService管理acl資訊

當我們新增了一條資訊,要在acl中記錄這條資訊的ID,所有者,以及對應的授權資訊。下列程式碼在新增資訊後執行,用於新增對應的acl資訊。
ObjectIdentity oid = new ObjectIdentityImpl(Message.class, message.getId());
MutableAcl acl = mutableAclService.createAcl(oid);
acl.insertAce(0, BasePermission.ADMINISTRATION,
    new PrincipalSid(owner), true);
acl.insertAce(1, BasePermission.DELETE,
    new GrantedAuthoritySid("ROLE_ADMIN"), true);
acl.insertAce(2, BasePermission.READ,
    new GrantedAuthoritySid("ROLE_USER"), true);
mutableAclService.updateAcl(acl);
        
第一步,根據class和id生成object的唯一標示。
第二步,根據object的唯一標示,建立一個acl。
第三步,為acl增加ace,這裡我們讓物件的所有者擁有對這個物件的“管理”許可權,讓“ROLE_ADMIN”擁有對這個物件的“刪除”許可權,讓“ROLE_USER”擁有對這個物件的“讀取”許可權。
最後,更新acl資訊。
當我們刪除物件時,也要刪除對應的acl資訊。下列程式碼在刪除資訊後執行,用於刪除對應的acl資訊。
ObjectIdentity oid = new ObjectIdentityImpl(Message.class, id);
mutableAclService.deleteAcl(oid, false);
        
使用class和id可以唯一標示一個物件,然後使用deleteAcl()方法將物件對應的acl資訊刪除。

23.3. 使用acl控制delete操作

上述程式碼中,除了物件的擁有者之外,我們還允許“ROLE_ADMIN”也可以刪除物件,但是我們不會允許除此之外的其他使用者擁有刪除物件的許可權,為了限制物件的刪除操作,我們需要修改Spring Security的預設配置。
首先要增加一個對delete操作起作用的表決器。
<bean id="aclMessageDeleteVoter" class="org.springframework.security.vote.AclEntryVoter">
    <constructor-arg ref="aclService"/>
    <constructor-arg value="ACL_MESSAGE_DELETE"/>
    <constructor-arg>
        <list>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.DELETE"/>
        </list>
    </constructor-arg>
    <property name="processDomainObjectClass" value="com.family168.springsecuritybook.ch12.Message"/>
</bean>
        
它只對Message這個類起作用,而且可以限制只有管理和刪除許可權的使用者可以執行刪除操作。
然後要將這個表決器新增到AccessDecisionManager中。
<bean id="aclAccessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <property name="decisionVoters">
        <list>
            <bean class="org.springframework.security.vote.RoleVoter"/>
            <ref local="aclMessageDeleteVoter"/>
        </list>
    </property>
</bean>
        
現在AccessDecisionManager中有兩個表決器了,除了預設的RoleVoter之外,又多了一個我們剛剛新增的aclMessageDeleteVoter。
現在可以把新的AccessDecisionManager賦予全域性方法許可權管理器了。
<global-method-security secured-annotations="enabled"
    access-decision-manager-ref="aclAccessDecisionManager"/>
        
然後我們就可以在MessageService.java中使用Secured註解,控制刪除操作了。
@Transactional
@Secured("ACL_MESSAGE_DELETE")
public void remove(Long id) {
    Message message = this.get(id);
    list.remove(message);
    ObjectIdentity oid = new ObjectIdentityImpl(Message.class, id);
    mutableAclService.deleteAcl(oid, false);
}
        
實際上,我們最好不要讓沒有許可權的操作者看到remove這個連結,可以使用taglib隱藏當前使用者無權看到的資訊。
<sec:accesscontrollist domainObject="${item}" hasPermission="8,16">
      |
      <a href="message.do?action=remove&id=${item.id}">Remove</a>
</sec:accesscontrollist>
        
8, 16是acl預設使用的掩碼,8表示DELETE,16表示ADMINISTRATOR,當使用者不具有這些許可權的時候,他在頁面上就看不到remove連結,也就無法執行操作了。
這比讓使用者可以執行remove操作,然後跑出異常,警告訪問被拒絕要友好得多。

23.4. 控制使用者可以看到哪些資訊

當使用者無權檢視一些資訊時,我們需要配置afterInvocation,使用後置判斷的方式,將使用者無權檢視的資訊,從MessageService返回的結果集中過濾掉。
後置判斷有兩種形式,一種用來控制單個物件,另一種可以過濾集合。
<bean id="afterAclRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationProvider">
    <sec:custom-after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.READ"/>
        </list>
    </constructor-arg>
</bean>
<bean id="afterAclCollectionRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
    <sec:custom-after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.READ"/>
        </list>
    </constructor-arg>
</bean>
        
afterAclRead可以控制單個物件是否可以顯示,afterAclCollectionRead則用來過濾集合中哪些物件可以顯示。[6]
對這兩個bean都是用了custom-after-invocation-provider標籤,將它們加入的後置判斷的行列,下面我們為MessageService.java中的對應方法新增Secured註解,之後它們就可以發揮效果了。
@Secured({"ROLE_USER", "AFTER_ACL_READ"})
public Message get(Long id) {
    for (Message message : list) {
        if (message.getId().equals(id)) {
            return message;
        }
    }
    return null;
}
@Secured({"ROLE_USER", "AFTER_ACL_COLLECTION_READ"})
public List getAll() {
    return list;
}
        

以上就是

相關文章