Acegi是Spring Framework 下最成熟的安全系統,它提供了強大靈活的企業級安全服務,如完善的認證和授權機制,Http資源訪問控制,Method 調用訪問控制,Access Control List (ACL) 基于對象實例的訪問控制,Yale Central Authentication Service (CAS) 耶魯單點登陸,X509 認證,當前所有流行容器的認證適配器,Channel Security頻道安全管理等功能。
我著重說明一下appfuse是怎么使用acegi的:
1)web.xml:
Acegi通過實現了Filter接口的FilterToBeanProxy提供一種特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy來完成過濾功能,這好處是簡化了web.xml的配置,并且充分利用了Spring IOC的優勢。FilterChainProxy包含了處理認證過程的filter列表,每個filter都有各自的功能。
<filter-mapping>限定了FilterToBeanProxy的URL匹配模式,有如下的請求才會受到權限控制。
這里配置了其他的xml文件我們主要看/WEB-INF/security.xml。
2)過濾的代理:
在security中我們首先看到的是過濾代理:
其中CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON是 將請求轉變成為小寫。
PATTERN_TYPE_APACHE_ANT是按照ant的方式進行匹配模式
FilterChainProxy會按順序來調用這些filter,使這些filter能享用Spring ioc的功能。
3)基本驗證管理:
我們先看一下它是怎么進行驗證管理的。
首先daoAuthenticationProvider是從數據庫中讀取數據進行驗證。
userDetailsService屬性使用了userDao進行的讀取(userDao實現org.acegisecurity.userdetails.UserDetailsService接口,而user類實現org.acegisecurity.userdetails.UserDetails才能userDetailsService里使用),還可以使用其他的方式比如直接鏈接數據源等等。
userCache屬性注明使用緩存,對于不經常改變的user放到緩存里可以大大減少數據庫的負擔。也可以選擇不使用,如果不使用則每次都登錄都從數據中查詢用戶信息。
passwordEncoder屬性則注明密碼加密的方法,
使用加密器對用戶輸入的明文進行加密。Acegi提供了三種加密器:
PlaintextPasswordEncoder—默認,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密
這里使用的哈希算法(SHA)加密。
其次,anonymousAuthenticationProvider是給與匿名用戶一個匿名的權限anonymous。
最后,rememberMeAuthenticationProvider則是把登錄的權限存儲在cookie里,不loginout時關閉瀏覽器,再度打開瀏覽器依舊保留權限(密碼修改時有特殊的限制)。
驗證管理者(authenticationManager)包含三個驗證提供者(daoAuthenticationProvider, anonymousAuthenticationProvider,rememberMeAuthenticationProvider),當然可以適當的簡略。
怎么進行驗證的呢?
3)過濾:
1.HttpSessionContextIntegrationFilter
每次request前 HttpSessionContextIntegrationFilter從Session中獲取Authentication對象,在request完后, 又把Authentication對象保存到Session中供下次request使用,此filter必須其他Acegi filter前使用,使之能跨越多個請求。
2. logoutFilter
該Filter負責處理退出登錄后所需要的清理工作。它會把session銷毀,把ContextHolder清空, 把rememberMeServices從cookies中清除掉,然后重定向到指定的退出登陸頁面。
3. authenticationProcessingFilter
該Filter負責處理登陸身份驗證。當接受到與filterProcessesUrl所定義相同的請求時,它會首先通過AuthenticationManager來驗證用戶身份。如果驗證成功,則重定向到defaultTargetUrl所定義的成功登陸頁面。如果驗證失敗,則再從rememberMeServices中獲取用戶身份,若再獲取失敗,則重定向到authenticationFailureUrl所定義登陸失敗頁面。
配置如下:
authenticationFailureUrl定義登陸失敗時轉向的頁面
defaultTargetUrl定義登陸成功時轉向的頁面
filterProcessesUrl定義登陸請求的頁面
rememberMeServices用于在驗證成功后添加cookie信息
4.securityContextHolderAwareRequestFilter
該Filter負責通過Decorate Model(裝飾模式),裝飾的HttpServletRequest對象。其Wapper是ServletRequest包裝類HttpServletRequestWrapper的子類(SavedRequestAwareWrapper或SecurityContextHolderAwareRequestWrapper),附上獲取用戶權限信息,request參數,headers, Date headers 和 cookies 的方法。
p.s. 如果把channelProcessingFilter放到securityContextHolderAwareRequestFilter前面則進行SSL的過濾。
5.rememberMeProcessingFilter
該Filter負責在用戶登錄后在本地機上記錄用戶cookies信息,免除下次再次登陸。檢查AuthenticationManager 中是否已存在Authentication對象,如果不存在則會調用RememberMeServices的aotoLogin方法來從cookies中獲取Authentication對象
6.anonymousProcessingFilter
該Filter負責為當不存在任何授權信息時,自動為Authentication對象添加userAttribute中定義的匿名用戶權限
7.exceptionTranslationFilter
該過濾器負責處理各種異常,然后重定向到相應的頁面中。
8.filterInvocationInterceptor
該過濾器會首先調用AuthenticationManager判斷用戶是否已登陸認證,如還沒認證成功,則重定向到登陸界面。認證成功,則并從Authentication中獲取用戶的權限。然后從objectDefinitionSource屬性獲取各種URL資源所對應的權限。最后調用AccessDecisionManager來判斷用戶所擁有的權限與當前受保華的URL資源所對應的權限是否相匹配。如果匹配失敗,則返回403錯誤(禁止訪問)給用戶。匹配成功則用戶可以訪問受保護的URL資源。
其中使用了accessDecisionManager,他是經過投票機制來決定是否可以訪問某一資源(URL或方法)。allowIfAllAbstainDecisions為false時如果有一個或以上的decisionVoters投票通過,則授權通過。可選的決策機制有ConsensusBased和UnanimousBased。
roleVoter表示必須是以rolePrefix設定的value開頭的權限才能進行投票,如AUTH_ , ROLE_等,實現用戶分組。
4)方法執行前權限驗證
可以把類MethodSecurityInterceptor注入到代理了的類中,實現在方法執行之前進行驗證。
aecgi的常用標簽(一共只有4個)
這個標簽來控制頁面元素的顯示與否。
主要有如下屬性
ifAnyGranted 只要有任意權限相當于或者(||)這個經常使用
ifAllGranted 需要有所列出的全部權限 相當于并且(&&)
ifNotGranted 不能有任意權限 相當于非(!)和第一個配合使用
用這些就可以控制頁面元素的顯示與否了
例:
結合了<authz:authorize>控制displayTag標簽的顯示。
我著重說明一下appfuse是怎么使用acegi的:
1)web.xml:
<filter> <filter-name>securityFilter</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>
Acegi通過實現了Filter接口的FilterToBeanProxy提供一種特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy來完成過濾功能,這好處是簡化了web.xml的配置,并且充分利用了Spring IOC的優勢。FilterChainProxy包含了處理認證過程的filter列表,每個filter都有各自的功能。
<filter-mapping>限定了FilterToBeanProxy的URL匹配模式,有如下的請求才會受到權限控制。
<filter-mapping> <filter-name>securityFilter</filter-name> <url-pattern>/j_security_check</url-pattern> </filter-mapping> <filter-mapping> <filter-name>securityFilter</filter-name> <url-pattern>/dwr/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>securityFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <filter-mapping> <filter-name>securityFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
這里配置了其他的xml文件我們主要看/WEB-INF/security.xml。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-*.xml,/WEB-INF/security.xml</param-value> </context-param>
2)過濾的代理:
在security中我們首先看到的是過濾代理:
<!-- ======================== FILTER CHAIN ======================= --> <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> </property> </bean>
其中CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON是 將請求轉變成為小寫。
PATTERN_TYPE_APACHE_ANT是按照ant的方式進行匹配模式
FilterChainProxy會按順序來調用這些filter,使這些filter能享用Spring ioc的功能。
3)基本驗證管理:
我們先看一下它是怎么進行驗證管理的。
<!-- 驗證管理 --> <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <!-- 從數據庫中讀取用戶信息驗證身份 --> <ref local="daoAuthenticationProvider"/> <!-- 匿名用戶身份認證 --> <ref local="anonymousAuthenticationProvider"/> <!-- 已存cookie中的用戶信息身份認證 --> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean> <!-- 從數據庫中讀取用戶信息驗證身份 --> <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDao"/> <property name="userCache" ref="userCache"/> <property name="passwordEncoder" ref="passwordEncoder"/> </bean> <!-- 匿名用戶身份認證 --> <bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"> <property name="key" value="anonymous"/> </bean> <!-- 已存cookie中的用戶信息身份認證 --> <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider"> <property name="key" value="appfuseRocks"/> </bean>
首先daoAuthenticationProvider是從數據庫中讀取數據進行驗證。
userDetailsService屬性使用了userDao進行的讀取(userDao實現org.acegisecurity.userdetails.UserDetailsService接口,而user類實現org.acegisecurity.userdetails.UserDetails才能userDetailsService里使用),還可以使用其他的方式比如直接鏈接數據源等等。
userCache屬性注明使用緩存,對于不經常改變的user放到緩存里可以大大減少數據庫的負擔。也可以選擇不使用,如果不使用則每次都登錄都從數據中查詢用戶信息。
passwordEncoder屬性則注明密碼加密的方法,
使用加密器對用戶輸入的明文進行加密。Acegi提供了三種加密器:
PlaintextPasswordEncoder—默認,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密
<bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.ShaPasswordEncoder"/>
這里使用的哈希算法(SHA)加密。
其次,anonymousAuthenticationProvider是給與匿名用戶一個匿名的權限anonymous。
最后,rememberMeAuthenticationProvider則是把登錄的權限存儲在cookie里,不loginout時關閉瀏覽器,再度打開瀏覽器依舊保留權限(密碼修改時有特殊的限制)。
驗證管理者(authenticationManager)包含三個驗證提供者(daoAuthenticationProvider, anonymousAuthenticationProvider,rememberMeAuthenticationProvider),當然可以適當的簡略。
怎么進行驗證的呢?
3)過濾:
1.HttpSessionContextIntegrationFilter
每次request前 HttpSessionContextIntegrationFilter從Session中獲取Authentication對象,在request完后, 又把Authentication對象保存到Session中供下次request使用,此filter必須其他Acegi filter前使用,使之能跨越多個請求。
2. logoutFilter
該Filter負責處理退出登錄后所需要的清理工作。它會把session銷毀,把ContextHolder清空, 把rememberMeServices從cookies中清除掉,然后重定向到指定的退出登陸頁面。
3. authenticationProcessingFilter
該Filter負責處理登陸身份驗證。當接受到與filterProcessesUrl所定義相同的請求時,它會首先通過AuthenticationManager來驗證用戶身份。如果驗證成功,則重定向到defaultTargetUrl所定義的成功登陸頁面。如果驗證失敗,則再從rememberMeServices中獲取用戶身份,若再獲取失敗,則重定向到authenticationFailureUrl所定義登陸失敗頁面。
配置如下:
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationFailureUrl" value="/login.jsp?error=true"/> <property name="defaultTargetUrl" value="/"/> <property name="filterProcessesUrl" value="/j_security_check"/> <property name="rememberMeServices" ref="rememberMeServices"/> </bean>
authenticationFailureUrl定義登陸失敗時轉向的頁面
defaultTargetUrl定義登陸成功時轉向的頁面
filterProcessesUrl定義登陸請求的頁面
rememberMeServices用于在驗證成功后添加cookie信息
4.securityContextHolderAwareRequestFilter
該Filter負責通過Decorate Model(裝飾模式),裝飾的HttpServletRequest對象。其Wapper是ServletRequest包裝類HttpServletRequestWrapper的子類(SavedRequestAwareWrapper或SecurityContextHolderAwareRequestWrapper),附上獲取用戶權限信息,request參數,headers, Date headers 和 cookies 的方法。
p.s. 如果把channelProcessingFilter放到securityContextHolderAwareRequestFilter前面則進行SSL的過濾。
5.rememberMeProcessingFilter
該Filter負責在用戶登錄后在本地機上記錄用戶cookies信息,免除下次再次登陸。檢查AuthenticationManager 中是否已存在Authentication對象,如果不存在則會調用RememberMeServices的aotoLogin方法來從cookies中獲取Authentication對象
6.anonymousProcessingFilter
該Filter負責為當不存在任何授權信息時,自動為Authentication對象添加userAttribute中定義的匿名用戶權限
7.exceptionTranslationFilter
該過濾器負責處理各種異常,然后重定向到相應的頁面中。
8.filterInvocationInterceptor
該過濾器會首先調用AuthenticationManager判斷用戶是否已登陸認證,如還沒認證成功,則重定向到登陸界面。認證成功,則并從Authentication中獲取用戶的權限。然后從objectDefinitionSource屬性獲取各種URL資源所對應的權限。最后調用AccessDecisionManager來判斷用戶所擁有的權限與當前受保華的URL資源所對應的權限是否相匹配。如果匹配失敗,則返回403錯誤(禁止訪問)給用戶。匹配成功則用戶可以訪問受保護的URL資源。
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="objectDefinitionSource"> <value> PATTERN_TYPE_APACHE_ANT /clickstreams.jsp*=admin /flushCache.*=admin /passwordHint.html*=ROLE_ANONYMOUS,admin,user /reload.*=admin /signup.html*=ROLE_ANONYMOUS,admin,user /users.html*=admin /**/*.html*=admin,user </value> </property> </bean>
其中使用了accessDecisionManager,他是經過投票機制來決定是否可以訪問某一資源(URL或方法)。allowIfAllAbstainDecisions為false時如果有一個或以上的decisionVoters投票通過,則授權通過。可選的決策機制有ConsensusBased和UnanimousBased。
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"> <property name="allowIfAllAbstainDecisions" value="false"/> <property name="decisionVoters"> <list> <bean class="org.acegisecurity.vote.RoleVoter"> <property name="rolePrefix" value=""/> </bean> </list> </property> </bean>
roleVoter表示必須是以rolePrefix設定的value開頭的權限才能進行投票,如AUTH_ , ROLE_等,實現用戶分組。
4)方法執行前權限驗證
<!-- 類代理 --> <aop:config> <aop:advisor id="managerSecurity" advice-ref="methodSecurityInterceptor" pointcut="execution(* cn.scs.service.UserManager.*(..))"/> <aop:advisor id="personManagerSecurity" advice-ref="methodSecurityInterceptor" pointcut="execution(* cn.scs.service.PersonManager.*(..))"/> </aop:config> <!-- 在執行方法前進行攔截,檢查用戶權限信息 --> <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="objectDefinitionSource"> <value> cn.scs.service.UserManager.getUsers=admin cn.scs.service.UserManager.removeUser=admin cn.scs.service.PersonManager.getPeople=ROLE_ANONYMOUS </value> </property> </bean>
可以把類MethodSecurityInterceptor注入到代理了的類中,實現在方法執行之前進行驗證。
aecgi的常用標簽(一共只有4個)
<authz:authorize>
這個標簽來控制頁面元素的顯示與否。
主要有如下屬性
ifAnyGranted 只要有任意權限相當于或者(||)這個經常使用
ifAllGranted 需要有所列出的全部權限 相當于并且(&&)
ifNotGranted 不能有任意權限 相當于非(!)和第一個配合使用
用這些就可以控制頁面元素的顯示與否了
例:
<authz:authorize ifAnyGranted="ROLE_ADMIN"> <display:column property="infoId" sortable="true" href="infoContentform.html?typeId=${typeId}" media="html" paramId="infoId" paramProperty="infoId" titleKey="infoContent.infoId"/> </authz:authorize> <authz:authorize ifNotGranted="ROLE_ADMIN"> <display:column property="infoId" sortable="true" titleKey="infoContent.infoId"/> </authz:authorize>
結合了<authz:authorize>控制displayTag標簽的顯示。