[收藏]Spring Security中的ACL

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;
}
        

以上就是

posted @ 2014-07-15 11:17  IT一族  阅读(1409)  评论(1编辑  收藏  举报