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], 有问题欢迎讨论