一般情況下,在ACEGI中隊filterChainProxy如下配置
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,captchaValidationProcessingFilter,
authenticationProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
logoutFilter,channelProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,
exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
當系統中受保護的Resource過多時,會出現這樣的一個問題,頁面加載速度明顯變慢了,特別是在用戶登錄系統后。為什么這么說呢,我們先分析下filterInvocationInterceptor(即org.acegisecurity.intercept.web.FilterSecurityInterceptor)的工作原理便可知道一二。
在FilterSecurityInterceptor的父類AbstractSecurityInterceptor中使用beforeInvocation方法對用戶訪問的資源進行抉擇,判斷用戶是否有訪問權限,這里主要是對URL進行判斷,在URL轉發之前判斷該用戶是否有訪問該URL的權限。這樣一來因為filterInvocationInterceptor對所有路徑進行過濾上面的(/**)設置,包括靜態圖片文件,css文件,flash文件等,這些url都要經過FilterSecurityInterceptor的判斷,這樣勢必影響頁面加載速度。那為什么登陸后系統會明顯變慢了,因為匿名用戶時,ACEGI讀取的匿名用戶的Resource列表為空,雖然也對所有的URL進行了權限判斷,但頁面加載并不顯得慢。經過上面的分析,嘗試了作了下面的修改,把每個Filter需要過濾的URL單獨寫,有公用的寫成一行,示例如下。作了這樣的優化,頁面加載的速度有了明顯的提升。
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/j_acegi_security_check=httpSessionContextIntegrationFilter,captchaValidationProcessingFilter,
authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
/j_acegi_logout=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,
anonymousProcessingFilter,logoutFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,
exceptionTranslationFilter,filterInvocationInterceptor
/**/*.html=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
basicProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
/**/*.htm=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
basicProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
/**/*.jsp=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
basicProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
/**/*.do=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
basicProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor
/**/*.ajax=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
basicProcessingFilter,securityContextHolderAwareRequestFilter,ajaxExceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
value部分一行顯示不下,我手動折行了
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1827059
Acegi with Tapestry Updating...
Filter類似于AOP的Round Device,可以在實際業務的前后加入自定義的操作。主要是通過Filter來過濾Request,然后加入Authentication、Authorization之類的安全過程。
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!---->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**= httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,
exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
<!-- -->
PATTERN_TYPE_APACHE_ANT:使用ANT類型的正則表達式,如果沒有這句話使用的是Perl樣式的正則表達式
/**=后面的部分就是使用的一些Filters,他們都是Spring context中的Bean,下面我一一解釋。
每次request前 HttpSessionContextIntegrationFilter從Session中獲取Authentication對象,在request完后, 又把Authentication對象保存到Session中供下次request使用,此filter必須其他Acegi filter前使用。
起到認證管理的作用,它將驗證的功能委托給多個Provider,并通過遍歷Providers, 以保證獲取不同來源的身份認證,若某個Provider能
用于處理HTTP頭的認證信息,如從Spring遠程協議(如Hessian和Burlap)或普通的瀏覽器如IE,Navigator的HTTP頭中獲取用戶信息,將他們轉交給通過authenticationManager屬性裝配的認證管理器。如果認證成功,會將一個Authentication對象放到會話中,否則,如果認證失敗,會將控制轉交給認證入口點(通過authenticationEntryPoint屬性裝配)。
在Acegi1.0.0中是SecurityEnforcementFilter,Acegi1.0.0 RC2中將之拋棄,用以上兩個Filter來作為攔截器配合使用,并導向認證點。
它的屬性authenticationEntryPoint定義了拋出異常后定向到什么認證點。如果為authenticationProcessingFilterEntryPoint則為Form驗證,定向到一個login頁面由用戶輸入用戶名密碼,提交后請求會通過authenticationProcessingFilter去Provider驗證。如果為
相當于一個攔截器,他的屬性objectDefinitionSource定義了什么樣的資源需要認證,他還有屬性authenticationManager和
通道過濾器,他可以定義什么樣的request可以在Http下傳輸,什么樣的request可以在Https下傳輸,進行安全通道的轉換。這個過濾器一般緊跟著httpSessionContextIntegrationFilter
下面我將用配置文件來詳細說明
authenticationManager
authenticationManager是最核心的一個manager,很多filter中都要引用他來進行認證,他含有很多Provider,每個Provider負責具體的認證工作,比如下面的就是通過Dao來進行認證,還有通過Cas中央認證,Jaas認證等方式,這里不多說,查查文檔吧。
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<ref bean="inMemoryDaoImpl"/>
</property>
< !--
property name="saltSource">
<ref bean="saltSource"/>
</property>
<property name="passwordEncoder">
<ref bean="passwordEncoder"/>
</property name="userCache" ref="userCache"/
-->
</bean>
passwordEncoder為編碼方式,有明文、MD5編碼、SHA編碼等,可選。
userCache為緩存方式,可選。
inMemoryDaoImpl即通過內存中的認證信息認證,如下。
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
ck=ck,ROLE_USER
</value>
</property>
</bean>
ck=ck,ROLE_USER表明其中一個用戶名為ck,密碼ck,角色是ROLE_USER。
<!-- httpSessionContextIntegrationFilter -->
<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
<!-- -->
<bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">
<property name="channelDecisionManager">
<ref bean="channelDecisionManager"/>
</property>
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A.*storeroom.*\Z=REQUIRES_SECURE_CHANNEL
</value>
</property>
</bean>
其中的正則表達式規定了什么樣的URL請求需要轉成安全(Https)或非安全(Http)的請求
<property name="channelProcessors">
<list>
<ref bean="secureChannelProcessor"/>
<ref bean="insecureChannelProcessor"/>
</list>
</property>
</bean>
<bean id="insecureChannelProcessor" class="org.acegisecurity.securechannel.InsecureChannelProcessor"/>
<!-- -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_security_check</value>
</property>
<property name="authenticationFailureUrl">
<value>/app?service=page/MyLogin</value>
</property>
<property name="defaultTargetUrl">
<value>/</value>
</property>
</bean>
其中filterProcessesUrl是認證頁面輸入用戶名密碼的那個form的Action屬性的值
<!-- -->
<bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref local="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
</bean>
<!-- -->
<!-- EntryPoint -->
<bean id="basicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName"><value>Daisy Realm</value></property>
</bean>
<property name="loginFormUrl"><value>/app?service=page/MyLogin</value></property>
<property name="forceHttps"><value>false</value></property>
</bean>
其中loginFormUrl指明了導向到哪個認證頁面,由于是在Tapestry中,所以導向到頁面:
<!-- -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<ref local="authenticationProcessingFilterEntryPoint"/>
</property>
</bean>
其中的authenticationEntryPoint表明導向Form認證點還是導向Basic認證點
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref local="httpRequestAccessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A.*storeroom.*\Z=ROLE_USER
</value>
</property>
</bean>
其中的objectDefinitionSource定義了什么樣的資源需要什么樣的角色。自我推測\A \Z表示正則表達式的開始和結束,.表示任意字符,*表示前面一個字符出現0到n次,所以\A.*storeroom.*\Z表示URL出現storeroom子串的請求,主要這里要全變為小寫才匹配。能執行這個請求得到結果必須具有權限ROLE_USER
<!-- -->
<form name="logindata" method="post" action="j_acegi_security_check">
<table>
<tr>
<td>id :</td>
<td><input type="text" name="j_username" size="25" value="st001"></td>
</tr>
<tr>
<td>password:</td>
<td><input type="password" name="j_password" size="25" value="123456"/></td>
</tr>
<tr>
<td align="right" nowrap><input type="submit" value="submit"></td>
<td width="100%"> </td>
</tr>
</table>
</form>
在jdbcDaoImpl中賦予用戶ROLE_SHENTING,
在filterInvocationInterceptor的objectDefinitionSource中定義了各種URL所需的ROLE_XXX
filterInvocationInterceptor的accessDecisionManager用RoleVoter來匹配以上的權限
b.對與第二種:
在context中基本不涉及,可以不用考慮,
JdbcDaoImpl中賦予權限例:
<property name="dataSource"><ref bean="dataSource"/></property>
<property name="rolePrefix" value=""></property>
<property name="usersByUsernameQuery"
value="SELECT yhbh as username,ma as password,'10' as enabled FROM yh_user WHERE yhbh = ? and scbz = '0'"/>
<property name="authoritiesByUsernameQuery"
value="SELECT yhbh as username,'ROLE_SHENTIN' as authority FROM yh_user WHERE yhbh = ?"/>
</bean>
summary:
Normal Authentication
1、filterInvocationInterceptor對請求進行攔截,objectDefinitionSource表示了URL的對應權限。
2. filterInvocationInterceptor的authenticationManager和accessDecisionManager起作用,驗證身份,檢查權限
如果符合條件就OK,否則拋出異常
3. exceptionTranslationFilter攔截異常,重定向用戶到EntryPoint
4. Form頁面,用戶輸入用戶名和密碼,點擊確認后Form的action為j_acegi_security_check
5. 即請求中含"j_acegi_security_check"字符串,和authenticationProcessingFilter的filterProcessesUrl匹配,
6. 于是此請求被authenticationProcessingFilter截取,送到authenticationProcessingFilter的authenticationManager
驗證身份,如果通過就OK。
---------------------------------------------------------------------------------------------------
CAS Authentication
1. 同上,filterInvocationInterceptor對請求進行攔截,objectDefinitionSource表示了URL的對應權限。
2. 同上,filterInvocationInterceptor的authenticationManager和accessDecisionManager起作用,驗證身份,檢查權限
如果符合條件就OK,否則拋出異常
3. 同上,exceptionTranslationFilter攔截異常,重定向用戶到EntryPoint
casProcessingFilterEntryPoint的loginUrl為CAS服務器的驗證地址,serviceProperties為成功驗證后
CAS服務器重定向的地址j_acegi_cas_security_check(相當于上面的action,但上面的尚未驗證,這里已驗證成功)
4. casProcessingFilter的filterProcessesUrl和服務器身份驗證成功后的請求匹配(即含j_acegi_cas_security_check)
5. 于是請求被casProcessingFilter獲取,在CAS服務器端通過身份驗證后,CAS會將之重定向到一個服務URL,在Acegi中
還必須用authenticationManager的casAuthenticationProvider來處理Acegi這邊的身份驗證并檢查用戶權限。
6. casAuthenticationProvider的casAuthoritiesPopulator去Dao檢查身份和權限
casAuthenticationProvider的casProxyTicketValidator用來驗證當前請求的有效性
7. casProxyTicketValidator定義了CAS端驗證票據的地址,以及成功驗證后CAS服務器重定向的地址j_acegi_cas_security_check
Question:
1.Normal Authentication中,是不是要在步驟2和4中驗證兩次
Acegi學習小結
一、基本原理
Acegi認證授權主要基于兩大技術,一是Filter機制,二是AOP的攔截機制。通過FilterSecurityInterceptor很好地實現了對URI的保護,通過MethodSecurityInterceptor實現了對Service的方法的攔截保護,通過ACL 實現了對prototype類型的Object進行過濾和保護。
二、基本概念
HttpSessionContextIntegrationFilter 存儲SecurityContext in HttpSession
ChannelProcessingFilter 重定向到另一種協議,如http到https
ConcurrentSessionFilter 因為不使用任何SecurityContextHolder的功能,但是需要更新SessionRegistry來表示當前的發送請求的principal,通過在web.xml中注冊Listener監聽Session事件,并發布相關消息,然后由SessionRegistry獲得消息以判斷當前用戶的Session數量。
AuthenticationProcessingFilter 普通認證機制(大多數用這個)
CasProcessingFilter CAS認證機制
BasicProcessingFilter Http協議的Basic認證機制
HttpRequestIntegrationFilter Authentication 從容器的HttpServletRequest.getUserPrincipal()獲得
JbossIntegrationFilter 與Jboss相關。
SecurityContextHolderAwareRequestFilter 與servlet容器結合使用。
RememberMeProcessingFilter 基于Cookies方式進行認證。
AnonymousProcessingFilter 匿名認證。
ExceptionTranslationFilter 捕獲所有的Acegi Security 異常,這樣要么返回一個HTTP錯誤響應或者加載一個對應的AuthenticationEntryPoint
AuthenticationEntryPoint 認證入口
三、Acegi認證授權流程
1、FilterToBeanProxy 負責代理請求給FilterChainProxy
2、FilterChainProxy 方便的將多個Filter串聯起來,如上面基本概念中提到的各種Filter,當然如果對URI進行授權保護,也可以包含FilterSecurityInterceptor。注意各Filter的順序。
3、AbstractSecurityInterceptor 調度中心。負責調用各模塊完成相應功能。
FilterSecurityInterceptor 對URI進行攔截保護
AspectJSecurityInterceptor 對方法進行攔截保護
MethodSecurityInterceptor 對方法進行攔截保護
4、AuthenticationManager 用戶認證
-> AuthenticationProvider 實際進行用戶認證的地方(多個)。
-> UserDetailsService 返回帶有GrantedAuthority的UserDetail或者拋出異常。
5、AccessDecisionManager(UnanimousBased/AffirmativeBased/ConsensusBased) 授權
-> AccessDecisionVoter(RoleVoter/BaseAclEntryVoter) 實際投票的Voter(多個).
6、RunAsManager 變更GrantedAuthority
7、AfterInvocationManager 變更返回的對象
-> BaseInvocationProvider 實際完成返回對象變更的地方(多個)。
四、Acegi實例
http://www.javaeye.com/topic/43341
再論 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 的實現, 只需要集成過來即可, 因此定義接口如下
/**
* <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, 我的配置如下
<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>
<!--
<property name="rejectPublicInvocations"><value>true</value></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, 有問題歡迎討論