`

再论 Acegi 权限存储策略 (转载)

阅读更多
在我之前的一篇文章里, 说明了在 Acegi 中如何将资源权限数据存储到数据库中, 文章见 http://www.hibernate.org.cn/viewtopic.php?t=17538,
虽然文中方式实现了从数据库读取资源权限, 但是代码量较大, 并且重载了 SecurityEnforcementFilter, 造成比较大的侵入性,
这里我将提供另一种更简洁的方式实现此功能.

入口还是 org.acegisecurity.intercept.web.FilterSecurityInterceptor, 资源权限配置来自于
objectDefinitionSource, 标准配置方式采用 FilterInvocationDefinitionSourceEditor 解析, 支持 Perl5 和 AntPath 两种风格,
为了实现从其它位置(典型如数据库), 我们要做的就是实现一个自定义的 FilterInvocationDefinitionSource, 查看类层次结构图后可以发现,
acegi 中已经有一个 AbstractFilterInvocationDefinitionSource 已经实现此接口, 只要实现一个抽象方法即可


代码
public abstract ConfigAttributeDefinition lookupAttributes(String url);  


因此, 自定义一个 Class 如下 :


代码
public class RdbmsBasedFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource   


因为 acegi 中已经提供了 Perl5 和 AntPath 的实现, 只需要集成过来即可, 因此定义接口如下


代码
 
/**  
* <class>ConfigableFilterInvocationDefinition</class> 支持 Perl5 和 ant Path 两种风格的资源配置方式  
* @since 2006-1-19  
* @author 王政  
* @version $Id: ConfigableFilterInvocationDefinition.java,v 1.3 2006/01/19 09:40:37 wz Exp $  
*/  
public interface ConfigableFilterInvocationDefinition {  
      
    /** The Perl5 expression  */  
    String PERL5_KEY = "PATTERN_TYPE_PERL5";  
      
    /** The ant path expression */  
    String ANT_PATH_KEY = "PATTERN_TYPE_APACHE_ANT";  
      
    /** 标准分隔符 */  
    String STAND_DELIM_CHARACTER = ",";  
      
    /**  
     * Set resource expression, the value must be {@link #PERL5_KEY_REG_EXP} or {@link #ANT_PATH_KEY}  
     * @see #REOURCE_EXPRESSION_PERL5_REG_EXP  
     * @see #RESOURCE_EXPRESSION_ANT_PATH_KEY  
     * @param resourceExpression the resource expression  
     */  
    void setResourceExpression(String resourceExpression);  
      
    /**  
     *   
     * @return resource expression  
     */  
    String getResourceExpression();  
      
    /**  
     * Set whether convert url to lowercase before comparison  
     * @param convertUrlToLowercaseBeforeComparison whether convertUrlToLowercaseBeforeComparison  
     */  
    void setConvertUrlToLowercaseBeforeComparison(boolean convertUrlToLowercaseBeforeComparison);  
      
    /**  
     *   
     * @return whether convert url to lowercase before comparison  
     */  
    boolean isConvertUrlToLowercaseBeforeComparison();  
     
}  
 


再让 RdbmsBasedFilterInvocationDefinitionSource 实现此接口即可, 下面是简略代码


代码
/**  
* <class>RdbmsBasedFilterInvocationDefinitionSource</class> 是基于数据库的权限存储实现, 它支持两种风格的配置  
* @see com.skyon.uum.security.acegi.intercept.web.ConfigableFilterInvocationDefinition  
* @see org.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap  
* @see org.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap  
* @since 2006-1-19  
* @author 王政  
* @version $Id: RdbmsBasedFilterInvocationDefinitionSource.java,v 1.6 2006/02/13 03:20:55 wz Exp $  
*/  
public class RdbmsBasedFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource   
    implements ConfigableFilterInvocationDefinition, InitializingBean {  
      
      
    //~ Static fields/initializers =============================================  
 
    private static final Log logger = LogFactory.getLog(RdbmsBasedFilterInvocationDefinitionSource.class);  
      
    //  ~ Instance fields ========================================================  
      
    private String resourceExpression = PERL5_KEY;  
      
    private boolean convertUrlToLowercaseBeforeComparison = false;  
          
    private ResourceMappingProvider resourceMappingProvider;  
      
    //  ~ Methods ================================================================  
      
    /**  
     *   
     * @see org.acegisecurity.intercept.web.AbstractFilterInvocationDefinitionSource#lookupAttributes(java.lang.String)  
     */  
    public ConfigAttributeDefinition lookupAttributes(String url) {  
        FilterInvocationDefinitionSource actualSource = populateFilterInvocationDefinitionSource();  
          
        if (RegExpBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)) {  
            return ((RegExpBasedFilterInvocationDefinitionMap) actualSource).lookupAttributes(url);  
        } else if (PathBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)) {  
            return ((PathBasedFilterInvocationDefinitionMap) actualSource).lookupAttributes(url);  
        }  
          
        throw new IllegalStateException("wrong type of " + actualSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class   
                + " or " + PathBasedFilterInvocationDefinitionMap.class);         
    }  
      
    /**  
     *   
     * @see org.acegisecurity.intercept.ObjectDefinitionSource#getConfigAttributeDefinitions()  
     */  
    public Iterator getConfigAttributeDefinitions() {  
        FilterInvocationDefinitionSource actualSource = populateFilterInvocationDefinitionSource();  
          
        if (RegExpBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)) {  
            return ((RegExpBasedFilterInvocationDefinitionMap) actualSource).getConfigAttributeDefinitions();  
        } else if (PathBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)) {  
            return ((PathBasedFilterInvocationDefinitionMap) actualSource).getConfigAttributeDefinitions();  
        }  
          
        throw new IllegalStateException("wrong type of " + actualSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class   
                + " or " + PathBasedFilterInvocationDefinitionMap.class);     
    }  
      
      
    private FilterInvocationDefinitionSource populateFilterInvocationDefinitionSource() {                 
        FilterInvocationDefinitionMap definitionSource = null;  
          
        if (PERL5_KEY.equals(getResourceExpression())) {  
            definitionSource = new RegExpBasedFilterInvocationDefinitionMap();  
        } else if (ANT_PATH_KEY.equals(getResourceExpression())) {  
            definitionSource = new PathBasedFilterInvocationDefinitionMap();  
        } else {  
            throw new IllegalArgumentException("wrong resourceExpression value");  
        }  
          
        definitionSource.setConvertUrlToLowercaseBeforeComparison(isConvertUrlToLowercaseBeforeComparison());  
          
        ResourceMapping[] mappings = getResourceMappingProvider().getResourceMappings();  
        if (mappings == null || mappings.length ==0) {  
            return (FilterInvocationDefinitionSource) definitionSource;  
        }  
          
        for (int i = 0; i < mappings.length; i++) {  
            ResourceMapping mapping = mappings[i];  
            String[] recipents = mapping.getRecipients();  
              
            if (recipents == null || recipents.length == 0) {  
                if (logger.isErrorEnabled()) {  
                    logger.error("Notice, the resource : " + mapping.getResourcePath() + " hasn't no recipents, it will access by any one ! ");  
                }  
                continue;  
            }  
              
            StringBuffer valueBuffer = new StringBuffer();  
            for (int j = 0; j < recipents.length; j++) {  
                valueBuffer.append(recipents[j]);  
                if (j < recipents.length - 1) {  
                    valueBuffer.append(STAND_DELIM_CHARACTER);  
                }  
            }  
            String value = valueBuffer.toString();                      
            addSecureUrl(definitionSource, mapping.getResourcePath(), value);  
         }  
       
        return (FilterInvocationDefinitionSource )definitionSource;  
    }  
      
      
    /**  
     * @param source  
     * @param name  
     * @param value  
     * @throws IllegalArgumentException  
     */  
    private synchronized void addSecureUrl(FilterInvocationDefinitionMap source, String name, String value)   
        throws IllegalArgumentException {  
          
        // Convert value to series of security configuration attributes  
        ConfigAttributeEditor configAttribEd = new ConfigAttributeEditor();  
        configAttribEd.setAsText(value);  
 
        ConfigAttributeDefinition attr = (ConfigAttributeDefinition) configAttribEd.getValue();  
 
        // Register the regular expression and its attribute  
        source.addSecureUrl(name, attr);  
    }  
      
    省去 getter, setter....  
 
}  
 


ResourceMappingProvider


代码
public interface ResourceMappingProvider {  
      
    String RESOURCE_PATH_PREFIX = "/";  
      
    /** 
     * Get Resource Mapping 
     * @return resource mapping 
     */ 
    ResourceMapping[] getResourceMappings();  
      
}  
 
public class ResourceMapping {  
      
    // url  
    private String resourcePath;  
      
    // 即角色  
    private String[] recipients = new String[0];  
         
    省去 getter, setter....  
 
}  
 


这样就很完美的既支持了从数据库的读取数据, 又可以自由选择 Perl5 和 AntPath 两种风格的配置, 最后不要忘了给 ResourceMappingProvider 加一层 cache, 我的配置如下


代码
<bean id="resourceCache" class="com.skyon.uum.security.acegi.intercept.web.cache.EhCacheBasedResourceCache"> 
        <property name="dataSource"> 
            <ref bean="dataSource"></ref> 
        </property> 
        <property name="cache"> 
            <bean parent="cacheTemplate">                 
                <property name="cacheName"><value>resourceCache</value></property> 
            </bean> 
        </property> 
        <property name="allResourcesQuery"> 
            <value> 
            select distinct t.id, t.id, t.parent_id, t.url, t.title, t.layer, t.type, t.application_id  
            from uum_resource t order by t.orderField  
            </value> 
        </property> 
    </bean> 
      
    <bean id="permissionCache" class="com.skyon.uum.security.acegi.intercept.web.cache.EhCacheBasedPermissionCache"> 
        <property name="dataSource"> 
            <ref bean="dataSource"></ref> 
        </property> 
        <property name="cache"> 
            <bean parent="cacheTemplate">                 
                <property name="cacheName"><value>permissionCache</value></property> 
            </bean> 
        </property> 
        <property name="recipentsResourceMappingQuery"> 
            <value> 
            select r.name, p.resource_id from uum_permission p left outer join uum_role r on p.role_id = r.id  
            </value> 
        </property>   
    </bean> 
      
              
    <bean id="resourceMappingProvider" class="com.skyon.uum.security.acegi.intercept.web.ResourceMappingProviderImpl" autowire="byType"/> 
 
    <!-- ======================== AUTHENTICATION ======================= --> 
      
    <!-- Note the order that entries are placed against the objectDefinitionSource is critical.  
         The FilterSecurityInterceptor will work from the top of the list down to the FIRST pattern that matches the request URL.  
         Accordingly, you should place MOST SPECIFIC (ie a/b/c/d.*) expressions first, with LEAST SPECIFIC (ie a/.*) expressions last --> 
    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> 
        <property name="authenticationManager"><ref local="authenticationManager"/></property> 
        <property name="accessDecisionManager"><ref local="accessDecisionManager"/></property> 
        <property name="objectDefinitionSource"><ref local="filterInvocationDefinitionSource"></ref></property> 
    </bean> 
      
    <bean id="filterInvocationDefinitionSource" class="com.skyon.uum.security.acegi.intercept.web.RdbmsBasedFilterInvocationDefinitionSource"> 
        <property name="resourceMappingProvider"> 
            <ref local="resourceMappingProvider"></ref> 
        </property> 
        <property name="resourceExpression"> 
            <value>PATTERN_TYPE_APACHE_ANT</value> 
        </property> 
    </bean> 
 


这段时间看了很多人对 Acegi 的评价, 有不少观点认为 Acegi 的配置太过繁琐, 其实权限控制本来就不是一件很轻松的事, Acegi 用 AOP 实现, 配置文件的确有些繁琐,
但是只要一个配置文件就解决了整个系统的权限问题, 可谓一劳永逸, 相比较在 Action 中实现应该还是利远大于弊, 也有人说用 WebWork 的 Interceptor 实现, 虽然也是不错的 solution,
但是不要忘了, 并不是所有的项目都使用 webwork , 假如有一个 struts 的项目, 权限控制就会有移植性的问题.

分享到:
评论

相关推荐

    Spring Acegi权限控制

    同时,还需要定义权限策略,如哪些URL需要被保护,以及对应角色的权限。 数据库设计方面,通常包括用户表、角色表和用户角色关联表。用户表存储用户基本信息,角色表存储角色信息,而用户角色关联表用于建立用户和...

    基于rbac模式的acegi权限管理

    在Acegi Security中,我们可以创建自定义的权限表达式和授权策略,以适应不同业务场景的需求。例如,我们可以通过定义`hasRole`、`isAuthenticated`等方法,来检查用户是否具有特定的角色或权限。Acegi还支持动态...

    最简单acegi权限管理实例

    在实际项目中,我们需要根据需求进行更复杂的定制,如添加更多的用户角色、权限,或者实现更复杂的认证策略。对于初学者,这是一个很好的动手实践机会,也是深入理解Acegi权限管理机制的关键步骤。

    acegi安全策略与CAS整合

    AceGI安全策略与CAS(Central Authentication Service)整合是企业级应用中常见的安全解决方案,它能够为Web应用程序提供统一的身份验证和授权服务。本文档旨在详细阐述这一整合过程,包括配置步骤、所需资源以及...

    acegi 权限控制按钮

    Acegi是Spring框架早期的一个安全模块,用于提供高级的安全性和权限控制。在本文中,我们将深入探讨Acegi如何实现精细的权限控制,直至按钮或HTML元素级别,以及如何将其部署到Tomcat服务器。 首先,Acegi的核心是...

    权限Acegi的使用

    Acegi Security,现已被Spring Security所取代,是Java EE应用程序中的一个强大且灵活的权限管理框架。它提供了全面的身份验证、授权和访问控制功能,旨在确保企业级应用的安全性。Acegi通过AOP(面向切面编程)的...

    acegi权限控制学习笔记

    Acegi权限控制学习笔记 Acegi安全框架是Spring Security的前身,它提供了一种强大的、灵活的、基于组件的安全解决方案,用于实现企业级应用的安全控制。在这个学习笔记中,我们将探讨两个关键点:身份认证成功后的...

    acegi

    - "db"可能指的是数据库相关的配置或日志,Acegi通常需要配置用户数据库来存储用户信息和权限数据。 - "aopacegi"可能是一个包含Acegi与AOP相关配置或实现的文件,可能涉及切面的定义和安全策略的配置。 Acegi ...

    使用acegi控制用户权限实例

    在本实例中,我们将深入探讨如何使用Acegi来控制用户的权限。Acegi Security已经被Spring Security替代,但其核心思想和机制仍然适用于现代的Spring Security。 首先,我们需要理解Acegi的基础概念。Acegi的核心是`...

    Acegi解决权限问题

    Acegi Security是Spring框架早期的一个安全模块,它为Java企业级应用提供了强大的权限管理解决方案。在本文中,我们将深入探讨Acegi如何解决权限问题,并通过分析提供的代码、文档和配置文件,来理解其实现机制。 ...

    Acegi安全权限管理手册

    Acegi安全权限管理手册是一本全面介绍Acegi安全框架在权限管理方面应用的权威指南。Acegi Security是Spring框架的一个扩展,它为Java应用程序提供了强大的安全性和权限控制功能。在这个手册中,读者将深入理解如何...

    Acegi 数据库配置安全策略 源代码及图解

    2. **数据库配置**:Acegi允许将安全配置存储在数据库中,这意味着安全策略可以动态地进行修改,而无需每次变动时都重新部署应用。这在权限复杂多变的企业级应用中非常实用,管理员可以直接在数据库层面进行权限的增...

    acegi权限控制与数据库配置在SSH中的初级使用

    &lt;br&gt;真正高效的acegi权限配置,有效为你节省大量时间,因为你不需要再花费大量时间耗费在网络上查找“关于acegi的配置”; &lt;br&gt;acegi中高级配置在后文将陆续推出,敬请时刻关注; &lt;br&gt;下载资源仅需4分,相信你...

    acegi实现用户权限

    开发者还可以通过实现自定义的UserDetailsService接口来获取和验证用户信息,这使得AceGI能够与现有的用户存储系统(如数据库或LDAP)集成。 另外,AceGI提供了丰富的日志和审计功能,可以帮助开发者追踪和分析安全...

    acegi+ssh动态实现基于角色的权限管理

    ### acegi+SSH动态实现基于角色的权限管理 在企业级应用开发中,权限管理和用户认证是必不可少的安全机制。本文将详细介绍如何利用Acegi安全框架(现称为Spring Security)结合SSH(Struts + Spring + Hibernate)...

    acegi java权限验证框架ppt讲座和代码

    Acegi Java权限验证框架是Spring Security的前身,它是一个强大且灵活的安全框架,用于Java企业级应用程序。这个框架提供了一套完整的解决方案,包括用户认证、访问控制、安全配置以及会话管理等多个方面,旨在帮助...

    关于web spring acegi 权限配置xml

    如何定义spring security的安全认证框架,对url和系统类method进行过滤以及权限分配和控制

    Acegi将资源权限数据存储到数据库.pdf

    在处理复杂的权限管理时,Acegi允许开发者将资源权限数据存储到数据库,而不是仅局限于配置文件中,这样可以实现更灵活的权限动态扩展。 在传统的Acegi配置中,资源和角色的关系通常是硬编码在XML配置文件内的,...

Global site tag (gtag.js) - Google Analytics