空间站

北极心空

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks
对ACEGI中FilterChainProxy进行性能调优

一般情况下,在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之类的安全过程。
    Acegi内置一些Filter,我们可以选择需要的加入我的Web Application,这些Filter是有顺序的,最好参照Acegi官方的reference
    Web Application使用Filter来截取请求,我们可以在web.xml 中的定义每一个需要的过滤器,但这样比较麻烦,好一点的做法是在Web.xm中定义一个org.acegisecurity.util.FilterChainProxy,这个东西可以认为是个Filter链,在这个链中定义需要的所有Filters。
从它名字FilterToBeanProxy大概也能猜到是个代理,实际代理Spring context里面类型为FilterChainProxy的target,FilterToBeanProxy会自己去寻找实现targetClass FilterChainProxy的Bean。
   
web.xml中示范代码:
 
<!-- Acegi Channel Processing Filter -->
 <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-mapping>
      <filter-name>Acegi Filter Chain Proxy</filter-name>
      <url-pattern>/*</url-pattern>
 </filter-mapping>
<!---->
 
Sprnig appContext中示范代码:
 
<!-- 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,authenticationProcessingFilter,basicProcessingFilter,
                           exceptionTranslationFilter,filterInvocationInterceptor
              </value>
     </property>
</bean>

<!-- -->
 其中
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON: 比较前全部转化为小写,所以记得在下面的Pattern用小写匹配。
PATTERN_TYPE_APACHE_ANT:使用ANT类型的正则表达式,如果没有这句话使用的是Perl样式的正则表达式
/**=后面的部分就是使用的一些Filters,他们都是Spring context中的Bean,下面我一一解释。
 
httpSessionContextIntegrationFilter
    每次request前 HttpSessionContextIntegrationFilter从Session中获取Authentication对象,在request完后, 又把Authentication对象保存到Session中供下次request使用,此filter必须其他Acegi filter前使用。
 
authenticationProcessingFilter
    起到认证管理的作用,它将验证的功能委托给多个Provider,并通过遍历Providers, 以保证获取不同来源的身份认证,若某个Provider能
成功确认当前用户的身份,authenticate()方法会返回一个完整的包含用户授权信息的Authentication对象,否则会抛出一个
AuthenticationException。比如Provider可以是DaoAuthenticationProvider、Cas...、Jaas...等。
 
basicProcessingFilter
    用于处理HTTP头的认证信息,如从Spring远程协议(如Hessian和Burlap)或普通的浏览器如IE,Navigator的HTTP头中获取用户信息,将他们转交给通过authenticationManager属性装配的认证管理器。如果认证成功,会将一个Authentication对象放到会话中,否则,如果认证失败,会将控制转交给认证入口点(通过authenticationEntryPoint属性装配)。
 
exceptionTranslationFilter,filterInvocationInterceptor
    在Acegi1.0.0中是SecurityEnforcementFilter,Acegi1.0.0 RC2中将之抛弃,用以上两个Filter来作为拦截器配合使用,并导向认证点。
 
exceptionTranslationFilter
    它的属性authenticationEntryPoint定义了抛出异常后定向到什么认证点。如果为authenticationProcessingFilterEntryPoint则为Form验证,定向到一个login页面由用户输入用户名密码,提交后请求会通过authenticationProcessingFilter去Provider验证。如果为
basicProcessingFilterEntryPoint则弹出一个对话框给用户输入用户名密码来验证。
 
filterInvocationInterceptor
    相当于一个拦截器,他的属性objectDefinitionSource定义了什么样的资源需要认证,他还有属性authenticationManager和
accessDecisionManager。
 
channelProcessingFilter
    通道过滤器,他可以定义什么样的request可以在Http下传输,什么样的request可以在Https下传输,进行安全通道的转换。这个过滤器一般紧跟着httpSessionContextIntegrationFilter
 

下面我将用配置文件来详细说明
这一部分为比较common一些的配置

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>
daoAuthenticationProvider也有几种实现方式,可以通过内存中的认证信息认证(不实用),也可以通过RDBMS信息来认证,还有一些,查查文档吧!
<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>
saltSource定义了编码密钥,可选。
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。
实际项目中一般是使用JdbcDaoImpl,去数据库获取认证信息,waiting for updating...
accessDecisionManager waiting for updating...
 

以下是filter配置
<!-- httpSessionContextIntegrationFilter -->
<bean id="httpSessionContextIntegrationFilter"
 class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
<!-- -->
 
<!-- ChannelProcessingFilter -->
<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)的请求
 
<bean id="channelDecisionManager" class="org.acegisecurity.securechannel.ChannelDecisionManagerImpl">
  <property name="channelProcessors">
    <list>
      <ref bean="secureChannelProcessor"/>
      <ref bean="insecureChannelProcessor"/>
    </list>
  </property>
</bean>
<bean id="secureChannelProcessor" class="org.acegisecurity.securechannel.SecureChannelProcessor"/>
<bean id="insecureChannelProcessor" class="org.acegisecurity.securechannel.InsecureChannelProcessor"/>
<!-- -->
 
<!-- authenticationProcessingFilter -->
<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属性的值
<!-- -->
 
<!-- basicProcessingFilter -->
<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>
 
<bean id="authenticationProcessingFilterEntryPoint"  
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
    <property name="loginFormUrl"><value>/app?service=page/MyLogin</value></property>
    <property name="forceHttps"><value>false</value></property>
</bean>
其中loginFormUrl指明了导向到哪个认证页面,由于是在Tapestry中,所以导向到页面:
上下文myapp/appservice=page/MyLogin, MyLogin是已经在tapestry application中定义了的页面,注意URL的形式。
<!-- -->
 
<!-- exceptionTranslationFilter, filterInvocationInterceptor -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint">
     <ref local="authenticationProcessingFilterEntryPoint"/>
    </property>
</bean>
其中的authenticationEntryPoint表明导向Form认证点还是导向Basic认证点
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
      <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
<!-- -->
 
注1:认证form类似于
<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%">&nbsp;</td>
    </tr>
  </table>
</form>
 
注2:关于用户权限
要分清两种权限,一个是context中直接定义的ROLE_SHENTING, ROLE_xx 等,还有一个是在数据库中定义的自定义权限编码,
  a. 对与第一种:
  在jdbcDaoImpl中赋予用户ROLE_SHENTING,
  在filterInvocationInterceptor的objectDefinitionSource中定义了各种URL所需的ROLE_XXX
  filterInvocationInterceptor的accessDecisionManager用RoleVoter来匹配以上的权限
  b.对与第二种:
  在context中基本不涉及,可以不用考虑,
 
  所以上面所有的授权全部是指的第一种,即定义在context中的ROLE_XXX
  JdbcDaoImpl中赋予权限例:
  <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.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>
   可见第一个sql验证username和password的是否对应,第二个sql直接给了ROLE_的权限,和数据库中自定义的权限编号无关
 

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 关键组件详述-1

1.Filter 组件

HttpSessionContextIntegrationFilter

该Filter负责每次请求前从HttpSession中获取Authentication对象,然后把Authentication存于一个新的ContextHolder对象(其实质上只是一个ThreadLocal对象)中,则让该次请求过程中的任何Filter都可以通过ContextHolder来共享Authentication,而不需要从HttpSession中取,减少传HttpRequest参数的麻烦。在请求完后把Authentication对象保存到HttpSession中供下次请求使用, 最后把刚才生成的ContextHolder对象销毁。这样就达到了让Authentication对象跨越多个请求的目的。

注意此filter须在调用其他Acegi filter前使用:

<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
</bean>

      AuthenticationProcessingFilter

该Filter负责处理登陆身份验证。当接受到与filterProcessesUrl所定义相同的请求时,它会首先通过AuthenticationManager来验证用户身份。如果验证成功,则重定向到defaultTargetUrl所定义的成功登陆页面。如果验证失败,则再从rememberMeServices中获取用户身份,若再获取失败,则重定向到authenticationFailureUrl所定义登陆失败页面。

<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
      <property name="authenticationFailureUrl"><value>/acegilogin.jsp?login_error=1</value></property>
      <property name="defaultTargetUrl"><value>/</value></property>
      <property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
      <property name="rememberMeServices"><ref local="rememberMeServices"/></property>
</bean>

      LogoutFilter

该Filter负责处理退出登录后所需要的清理工作。它会把session销毁,把ContextHolder清空, 把rememberMeServices从cookies中清除掉,然后重定向到指定的退出登陆页面。

<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout ->
      <constructor-arg>
         <list>
              <ref bean="rememberMeServices"/>
              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
         </list>
      </constructor-arg>
</bean>

      FilterInvocationInterceptor

该过滤器会首先调用AuthenticationManager判断用户是否已登陆认证,如还没认证成功,则重定向到登陆界面。认证成功,则并从Authentication中获取用户的权限。然后从objectDefinitionSource属性获取各种URL资源所对应的权限。最后调用AccessDecisionManager来判断用户所拥有的权限与当前受保华的URL资源所对应的权限是否相匹配。如果匹配失败,则返回403错误(禁止访问)给用户。匹配成功则用户可以访问受保护的URL资源。

<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
      <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
      <property name="objectDefinitionSource">
         <value>
                                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                                PATTERN_TYPE_APACHE_ANT
                                /index.jsp=ROLE_ANONYMOUS,ROLE_USER
                                /hello.htm=ROLE_ANONYMOUS,ROLE_USER
                                /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
                                /switchuser.jsp=ROLE_SUPERVISOR
                                /j_acegi_switch_user=ROLE_SUPERVISOR
                                /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
                                /**=ROLE_USER
        </value>
      </property>
</bean>

      SecurityContextHolderAwareRequestFilter

该Filter负责通过Decorate Model(装饰模式),装饰的HttpServletRequest对象。其Wapper是ServletRequest包装类HttpServletRequestWrapper的子类(SavedRequestAwareWrapper或SecurityContextHolderAwareRequestWrapper),附上获取用户权限信息,request参数,headers, Date headers 和 cookies 的方法。

<bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" />

      BasicProcessingFilter

该Filter负责处理HTTP头的认证信息,如从Spring远程协议(如Hessian和Burlap)或普通的浏览器如IE,Navigator的HTTP头中获取用户信息,将他们转交给通过authenticationManager属性装配的认证管理器。如果认证成功,会将一个Authentication对象放到会话中,否则,如果认证失败,会将控制转交给认证入口点(通过authenticationEntryPoint属性装配)

<bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
      <property name="authenticationManager"><ref local="authenticationManager"/></property>
      <property name="authenticationEntryPoint"><ref local="basicProcessingFilterEntryPoint"/></property>
   </bean>

   <bean id="basicProcessingFilterEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
      <property name="realmName"><value>Contacts Realm</value></property>
   </bean>

      RememberMeProcessingFilter

该Filter负责在用户登录后在本地机上记录用户cookies信息,免除下次再次登陆。检查AuthenticationManager 中是否已存在Authentication对象,如果不存在则会调用RememberMeServices的aotoLogin方法来从cookies中获取Authentication对象

    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
                   <property name="authenticationManager" ref="authenticationManager" />
                   <property name="rememberMeServices" ref="rememberMeServices" />
    </bean>

      AnonymousProcessingFilter

该Filter负责为当不存在任何授权信息时,自动为Authentication对象添加userAttribute中定义的匿名用户权限

    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
         <property name="key" value="changeThis" />
         <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
    </bean>

      ExceptionTranslationFilter

该过滤器负责处理各种异常,然后重定向到相应的页面中。

         <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
                   <property name="authenticationEntryPoint">
                            <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                                     <property name="loginFormUrl" value="/login.jsp" />
                                     <property name="forceHttps" value="false" />
                            </bean>
                   </property>
                   <property name="accessDeniedHandler">
                            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
                                     <property name="errorPage" value="/accessDenied.jsp" />
                            </bean>
                   </property>
         </bean>




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, 我的配置如下

<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>
  <!--
  <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, 有问题欢迎讨论

posted on 2007-12-25 09:35 芦苇 阅读(1865) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航: