再论 Acegi 权限存储策略

阅读更多
本文原出处
http://starcraft.blogdriver.com/starcraft/1135045.html

在我之前的一篇文章里, 说明了在 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 的实现, 只需要集成过来即可, 因此定义接口如下

/**
 * ConfigableFilterInvocationDefinition 支持 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 实现此接口即可, 下面是简略代码

/**
 * RdbmsBasedFilterInvocationDefinitionSource 是基于数据库的权限存储实现, 它支持两种风格的配置
 * @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, 我的配置如下


		
			
		
		
							
				resourceCache
			
        
		
			
			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
			
		
	
	
	
		
			
		
		
							
				permissionCache
			
        
		
			
			select r.name, p.resource_id from uum_permission p left outer join uum_role r on p.role_id = r.id
			
			
	
	
			
	

    
    
    
    
        
        
        
    
	
	
		
			
		
		
			PATTERN_TYPE_APACHE_ANT
		
	



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

我的 msn : [email protected], 有问题欢迎讨论

你可能感兴趣的:(Acegi,Ant,Webwork,IE,Web)