安全对象访问控制
Acegi 分别通过 Servlet 过滤器和方法拦截器对 URL 资源和业务方法进行访问拦截,向授权用户开放访问请求拒绝未授权用户的访问。
匿名用户权限信息
Acegi 引入了一个匿名用户权限的概念,匿名用户权限定义了未登录用户所能访问到的程序资源,这些资源包括站点首页、登录页面、静态资源共享等。 Acegi 为匿名用户认证提供了三个类,它们分别是:
- AnonymousAuthenticationToken: 该类是 Authentication 的实现类,它保存着匿名用户的权限信息
- AnonymousAuthenticationProvider: 该类是 AuthenticationProvider 的实现类,它能根据 AnonymousAuthenticationToken 进行匿名用户认证
- AnonymousProcessingFilter: 该过滤器发现 SecurityContextHolder 中不存在 Authentication 时,自动添加一个 AnonymousAuthenticationToken 到 SecurityContextHolder 中。
- < bean id = "filterChainProxy" class = "org.acegisecurity.util.FilterChainProxy" >
- < property name = "filterInvocationDefinitionSource" >
- < value >
- CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
- PATTERN_TYPE_APACHE_ANT
- /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter, logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter
- </ value >
- </ property >
- </ bean >
- <!--匿名用户处理过滤器 -->
- < bean id = "anonymousProcessingFilter" class = "org.acegisecurity.providers.anonymous.AnonymousProcessingFilter" >
- < property name = "key" value = "anonymousUser" />
- <!--匿名用户用户属性 -->
- < property name = "userAttribute" value = "ANONYMOUSUSER,PRIV_ANONYMOUS" />
- </ bean >
- <!--匿名用户认证提供者 -->
- < bean id = "anonymousAuthenticationProvider"
- class = "org.acegisecurity.providers. anonymous.AnonymousAuthenticationProvider " >
- < property name = "key" value = "anonymousUser" />
- </ bean >
- < bean id = "authenticationManager"
- class = "org.acegisecurity.providers.ProviderManager" >
- < property name = "providers" >
- < list >
- …
- <!--将匿名用户认证提供者添加到认证管理器的列表中 -->
- < ref local = " anonymousAuthenticationProvider" />
- </ list >
- </ property >
- </ bean >
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter, logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter </value> </property> </bean> <!--匿名用户处理过滤器 --> <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"> <property name="key" value="anonymousUser"/> <!--匿名用户用户属性 --> <property name="userAttribute" value="ANONYMOUSUSER,PRIV_ANONYMOUS"/> </bean> <!--匿名用户认证提供者 --> <bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers. anonymous.AnonymousAuthenticationProvider "> <property name="key" value="anonymousUser"/> </bean> <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> … <!--将匿名用户认证提供者添加到认证管理器的列表中 --> <ref local=" anonymousAuthenticationProvider" /> </list> </property> </bean>
URL 资源是用户直接通过请求调用的安全对象, Acegi 通过基于 Servlet 过滤器的 FilterSecurityInterceptor 对所有的 URL 资源进行拦截,并施加有效的访问控制。
applicationContext-acegi-plugin.xml: URL资源访问控制
- < bean id = "filterChainProxy" class = "org.acegisecurity.util.FilterChainProxy" >
- < property name = "filterInvocationDefinitionSource" >
- < value >
- CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
- PATTERN_TYPE_APACHE_ANT
- /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter,filterSecurityInterceptor
- </ value >
- </ property >
- </ bean >
- <!--URL资源访问拦截器-->
- < bean id = "filterSecurityInterceptor" class = "org.acegisecurity.intercept.web.FilterSecurityInterceptor" >
- < property name = "authenticationManager" ref = "authenticationManager" />
- < property name = "asscessDecisionManager" ref = " asscessDecisionManager" />
- < property name = "objectDefinitionSource" >
- < value >
- CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
- PATTERN_TYPE_APACHE_ANT
- /login.jsp = PRIV_ANONYMOUS ,PRIV_COMMON
- /hello_1.jsp = PRIV_1
- /updateForum.jsp = PRIV_1
- </ value >
- </ property >
- </ bean >
- <!--HTTP请求访问决策管理器-->
- < bean id = "asscessDecisionManager" class = "org.acegisecurity.vote.AffirmativeBased" >
- <!--是否所有投票者弃权意即同意-->
- < property name = "allowIfAllAbstainDecisions" value = "true" />
- <!--投票者列表-->
- < property name = "decisionVoters" >
- < list >
- < ref bean = "roleVoter" />
- </ list >
- </ property >
- </ bean >
- < bean id = "roleVoter" class = "org.acegisecurity.vote.RoleVoter" >
- <!--对应的权限前缀-->
- < property name = "rolePrefix" value = "PRIV_" />
- </ bean >
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter,filterSecurityInterceptor </value> </property> </bean> <!--URL资源访问拦截器--> <bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="asscessDecisionManager" ref=" asscessDecisionManager"/> <property name="objectDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /login.jsp=PRIV_ANONYMOUS,PRIV_COMMON /hello_1.jsp=PRIV_1 /updateForum.jsp=PRIV_1 </value> </property> </bean> <!--HTTP请求访问决策管理器--> <bean id="asscessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"> <!--是否所有投票者弃权意即同意--> <property name="allowIfAllAbstainDecisions" value="true"/> <!--投票者列表--> < property name="decisionVoters"> <list> <ref bean="roleVoter"/> </list> </property> </bean> <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"> <!--对应的权限前缀--> <property name="rolePrefix" value="PRIV_"/> </bean>
拦截器将对所有 URL 请求进行处理:
1 .判断用户是否已经通过身份认证,如果未通过身份认证,调用身份认证管理器进行处理,这意味着请求将重定向到登录页面。
2 .如果通过身份认证,调用访问决策管理器判断用户是否有权访问目标的 URL 资源。
3 .访问决策管理器将组织投票者进行投票并根据投票结果给出是否有权访问的结果。
4. 如果无权访问,将招聘异常否则开放目标 URL 资源的访问。
我们定义的 AccessDecisionManager Bean ,它通过组织投票者对是否允许访问进行投票并得到最终的结果。所有投票者都实现 AccessDecisionVoter 接口,决策管理器投票者的 int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) 方法获得投票结果。有三个不同的投票结果:
ACCESS_ABSTAIN: 弃权
ACCESS_DENIED :拒绝
ACCESS_GRANTED :允许
根据不同决策方案, Acegi 在 org.acegisecurity.vote 包中提供了三个 AccessDecisionManager 的实现类:
AffirmativeBased: 有同意票策略
ConsensusBased: 少数服从多数策略
UnanimousBased :无反对票策略
异常转换过滤器
异常转换过滤器( ExceptionTranslationFilter )能够捕捉 Acegi 招聘的抛出的权限访问异常,并导向适合响应页面,大大提高了系统交互的友好性。
注意, exceptionTranslationFilter 必须位于 filterSecurityIntercetor 之前。
applicationContext-acegi-plugin.xml:异常转换过滤器
- < bean id = "filterChainProxy" class = "org.acegisecurity.util.FilterChainProxy" >
- < property name = "filterInvocationDefinitionSource" >
- < value >
- …
- /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor
- </ value >
- </ property >
- </ bean >
- < bean id = "exceptionTranslationFilter" class = "org.acegisecurity.ui.ExceptionTranslationFilter" >
- <!--如果是未通过身份认证引发的访问异常,将请求导向到认证入口-->
- < property name = "authenticationEntryPoint" >
- < ref local = "authenticationEntryPoint" />
- </ property >
- <!--越权的访问将导向到出错页面-->
- < property name = "asscessDeniedHandler" >
- < bean class = "org.acegisecurity.ui.AsscessDeniedHandlerImpl" >
- < property name = "errorPage" value = "/error.jsp" />
- </ bean >
- </ property >
- </ bean >
- < bean id = "authenticationEntryPoint" class = "org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint" >
- < property name = "loginFormUrl" value = "/index.jsp" />
- </ bean >
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> … /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor </value> </property> </bean> <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <!--如果是未通过身份认证引发的访问异常,将请求导向到认证入口--> <property name="authenticationEntryPoint"> <ref local="authenticationEntryPoint"/> </property> <!--越权的访问将导向到出错页面--> <property name="asscessDeniedHandler"> <bean class="org.acegisecurity.ui.AsscessDeniedHandlerImpl"> <property name="errorPage" value="/error.jsp"/> </bean> </property> </bean> <bean id="authenticationEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl" value="/index.jsp"/> </bean>
通过 AuthenticationProcessingFilterEntryPoint 学习定义用户登录页面的地址,在抛出 AuthenticationException 异常时,请求导向 index.jsp 登录页面。当抛出 AccessDeniedException 异常时,请求民向到 error.jsp 页面。
业务类方法访问控制
Acegi 为业务类安全对象添加环绕增强的切面,在业务类方法被访问前后就可以进行拦截并实施访问控制, Acegi 提供了两种实施环绕增强切面的方案, Spring AOP和 AspectJ, 这里我们看一下 Spring AOP 方案:
applicationContext-acegi-plugin.xml: 基于 ProxyFactoryBean 的代理
- < bean id = "methodSecurityInterceptor" class = "org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor" >
- < property name = "authenticationManager" ref = "authenticationManager" />
- < property name = "asscessDecisionManager" ref = " asscessDecisionManager" />
- < property name = "objectDefinitionSource" >
- < value >
- com.ccd.service.BbtForum.updateForum = PRIV_2
- </ value >
- </ property >
- </ bean >
- < bean id = "bbtForum" class = "org.springframework.aop.framework.ProxyFactoryBean" >
- < property name = "intercetorNames" >
- < list >
- < idref bean = "methodSecurityInterceptor" />
- </ list >
- </ property >
- < property name = "proxyTargetClass" value = "true" />
- < property name = "target" ref = "bbtForumTarget" />
- </ bean >
- < bean id ="bbtForumTarget" class ="com.ccd.service.BbtForum" />
<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="asscessDecisionManager" ref=" asscessDecisionManager"/> <property name="objectDefinitionSource"> <value> com.ccd.service.BbtForum.updateForum=PRIV_2 </value> </property> </bean> <bean id="bbtForum" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="intercetorNames"> <list> <idref bean="methodSecurityInterceptor"/> </list> </property> <property name="proxyTargetClass" value="true"/> <property name="target" ref="bbtForumTarget"/> </bean> <bean id="bbtForumTarget"class="com.ccd.service.BbtForum"/>
使用注解提供对象定义源信息
Acegi 支持通过注解提供对象定义源的无数据信息
- package com.ccd.service;
- import org.acegisecurity.annotation.Secured;
- import com.ccd.domain.Forum;
- public class BbtForum{
- @Secured ({ "PRIV_2" }) //该业务方法对应的权限
- public void updateForum(Forum forum){
- System.out.println("execute update Forum…" );
- }
- }
package com.ccd.service;
import org.acegisecurity.annotation.Secured;
import com.ccd.domain.Forum;
public class BbtForum{
@Secured({"PRIV_2"})//该业务方法对应的权限
public void updateForum(Forum forum){
System.out.println("execute update Forum…");
}
}
Acegi 通过 MethodDefinitionAttributes 自动扫描 Spring 容器中的 Bean ,获取注解表示对象定义元数据信息
applicationContext-acegi-plugin.xml: 基于注解的对象定义源
- < bean id = "methodSecurityInterceptor" class = "org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor" >
- < property name = "authenticationManager" ref = "authenticationManager" />
- < property name = "asscessDecisionManager" ref = " asscessDecisionManager" />
- < property name = "objectDefinitionSource" ref = "objectDefinitionSource" />
- </ bean >
- <!--根据Bean的注解元数据定义的对象定义源-->
- < bean id = "objectDefinitionSource" class = "org.acegisecurity.intercept.method.MethodDefinitionAttributes" >
- <!--获取@Secured的注解元数据-->
- < property name = "attributes" >
- < bean class = "org.acegisecurity.annotation.SecurityAnnotationAttributes" >
- </ property >
- </ bean >
<bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="asscessDecisionManager" ref=" asscessDecisionManager"/> <property name="objectDefinitionSource" ref="objectDefinitionSource"/> </bean> <!--根据Bean的注解元数据定义的对象定义源--> <bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes"> <!--获取@Secured的注解元数据--> <property name="attributes"> <bean class="org.acegisecurity.annotation.SecurityAnnotationAttributes"> </property> </bean>
注意, Acegi 也支持 Jakata 的 Commons Attributes 进行标注,这时必须将
- < bean class = "org.acegisecurity.annotation.SecurityAnnotationAttributes" />
<bean class="org.acegisecurity.annotation.SecurityAnnotationAttributes"/>
替换成
- < bean class = "org.springframework.metadata.commons.CommonsAttributes" />
<bean class="org.springframework.metadata.commons.CommonsAttributes"/>
使用 BeanNameAutoProxyCreator 进行批量代理
applicationContext-acegi-plugin.xml: 通过指定Bean名字进行批量代理
- < bean class = "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
- < property name = "intercetorNames" >
- < list >
- < value > methodSecurityInterceptor </ value >
- </ list >
- </ property >
- < property name = "beanNames" >
- < list >
- < value > bbtForum </ value >
- </ list >
- </ property >
- </ bean >
- < bean id = "bbtForum" class = "com.ccd.service.BbtForum" >
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="intercetorNames"> <list> <value>methodSecurityInterceptor</value> </list> </property> <property name="beanNames"> <list> <value>bbtForum</value> </list> </property> </bean> <bean id="bbtForum" class="com.ccd.service.BbtForum">
PS:本文主要摘自《精通Spring 2.x ——企业应用开发详解》