再論 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 已經實現此接口, 只要實現一個抽象方法即可
java代碼:? |
public abstract ConfigAttributeDefinition lookupAttributes(String url); |
因此, 自定義一個 Class 如下 :
java代碼:? |
public class RdbmsBasedFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource |
因為 acegi 中已經提供了 Perl5 和 AntPath 的實現, 只需要集成過來即可, 因此定義接口如下
java代碼:? |
/** * <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 實現此接口即可, 下面是簡略代碼
java代碼:? |
/** * <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 ============================================= ? ? privatestaticfinal Log logger = LogFactory.getLog(RdbmsBasedFilterInvocationDefinitionSource.class); ? ? ? ? ? ? //? ? ? ? ~ Instance fields ======================================================== ? ? ? ? ? ? privateString resourceExpression = PERL5_KEY; ? ? ? ? privateboolean 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); ? ? ? ? ? ? ? ? }elseif(PathBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)){ ? ? ? ? ? ? ? ? ? ? ? ? return((PathBasedFilterInvocationDefinitionMap) actualSource).lookupAttributes(url); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? throw newIllegalStateException("wrong type of " + actualSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? + " or " + PathBasedFilterInvocationDefinitionMap.class);? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? /** ? ? ? ? * ? ? ? ? * @see org.acegisecurity.intercept.ObjectDefinitionSource#getConfigAttributeDefinitions() ? ? ? ? */ ? ? ? ? publicIterator getConfigAttributeDefinitions(){ ? ? ? ? ? ? ? ? FilterInvocationDefinitionSource actualSource = populateFilterInvocationDefinitionSource(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if(RegExpBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)){ ? ? ? ? ? ? ? ? ? ? ? ? return((RegExpBasedFilterInvocationDefinitionMap) actualSource).getConfigAttributeDefinitions(); ? ? ? ? ? ? ? ? }elseif(PathBasedFilterInvocationDefinitionMap.class.isInstance(actualSource)){ ? ? ? ? ? ? ? ? ? ? ? ? return((PathBasedFilterInvocationDefinitionMap) actualSource).getConfigAttributeDefinitions(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? throw newIllegalStateException("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(); ? ? ? ? ? ? }elseif(ANT_PATH_KEY.equals(getResourceExpression())){ ? ? ? ? ? ? ? ? ? ? definitionSource = new PathBasedFilterInvocationDefinitionMap(); ? ? ? ? ? ? }else{ ? ? ? ? ? ? ? ? ? ? throw newIllegalArgumentException("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
java代碼:? |
public interface ResourceMappingProvider { ? ? ? ? ? ? String RESOURCE_PATH_PREFIX = "/"; ? ? ? ? ? ? /** ? ? ?* Get Resource Mapping ? ? ?* @return resource mapping ? ? ?*/ ? ? ResourceMapping[] getResourceMappings(); ? ? } publicclass ResourceMapping { ? ? ? ? // url ? ? privateString resourcePath; ? ? ? ? // 即角色 ? ? privateString[] recipients = newString[0]; ? ? ? ? ? ? 省去 getter, setter.... } |
這樣就很完美的既支持了從數據庫的讀取數據, 又可以自由選擇 Perl5 和 AntPath 兩種風格的配置, 最后不要忘了給 ResourceMappingProvider 加一層 cache, 我的配置如下
java代碼:? |
<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 的項目, 權限控制就會有移植性的問題.
我的 msn : shartcn@msn.com, 有問題歡迎討論
posted on 2006-04-28 23:42 Vincent.Chen 閱讀(278) 評論(0) 編輯 收藏