Vincent.Chan‘s Blog

          常用鏈接

          統計

          積分與排名

          網站

          最新評論

          再論 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)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 抚远县| 五大连池市| 海淀区| 高邑县| 温泉县| 漯河市| 盐山县| 永仁县| 昌图县| 汝阳县| 阿城市| 河南省| 沂水县| 洞头县| 虹口区| 武川县| 泰州市| 满洲里市| 读书| 曲麻莱县| 台南县| 巫溪县| 丰城市| 广西| 宁化县| 布尔津县| 泰和县| 伊宁县| 汝南县| 托克逊县| 博爱县| 久治县| 竹溪县| 宜川县| 高邑县| 北宁市| 金山区| 黄浦区| 高淳县| 六安市| 新野县|