注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

无线时代辐射无穷

抓紧生宝宝,小心辐射

 
 
 

日志

 
 

早期spring security版本acegi学习实践  

2008-10-08 10:47:24|  分类: 单点登录 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

 

       折腾了一个星期了,天天加班,终于有了点眉目,可以记下blog了。最近开始了公司一个BOSS系统的设计开发工作,100多页的功能需求和100多张的页面需求,刚看到的时候差点晕过去^_^。由于设计到用户的计费帐单等操作,对安全性要求就比较高,需求中对系统内的几乎所有操作都定义了权限点,用角色的概念组织这些权限点,也就是不同的权限点的组合称为一个角色,每一个系统的用户属于某一个角色,登陆后有自己相应的界面和可执行的操作,如果有人故意或无意的访问。本着君子性非异也,善假于物也的崇高思想,开始到网上找现成的。开始发现了一个叫做Kasai的第三方框架,宣传的不错,“Kasai is a 100% Java based authentication and authorization framework. It allows you to integrate into your application a granular, complete and manageable permission scheme. The goal of the framework is to provide a simple-to-use-yet-powerful security environment for multi-user applications. Unlike JAAS, Kasai provides a much higher security abstraction, it's targeted at the specific security requirements that arise in real-life applications such as Intranets, ERPs, CRMs, document managers, accounting systems, etc.”看起来很符合需求,但是载了一个后发现文档里到处都是todo,论坛上也没什么人用,很难找到支持,晕菜了。看到论坛里说acegi的人比较多,但是很多人的说法都是acegi是一个安全框架,不是权限管理框架,因为里面没有角色和权限的概念,只有它定义的权限点。但是聊胜于无嘛,还是down下来一个用用吧。

       看了一下上一篇blog中提到的那个中文介绍的地址,稍微有了些大体认识,然后看了demorefernece以及,发现真的印证了论坛上人们对它的评论:结构狂复杂,用法暴困难,没有看几分钟就昏过去了……后来在坛子上发现有人说John Wiley & Sons出版的《Professional Java Development with the Spring Framework》对acegi有详细的介绍,想必John Wiley & Sons不会出很菜的书吧,于是乎搞了个电子版,在第十章中有详细的acegi介绍,真是盛名之下无虚士,果然好书的是。既便如此,偶还是看了23天才基本看明白,其实中间走了一些岔路,因为一些思维定式往往会误导思路,走了一些弯路才弄明白。这里把个人遇到的一些问题记录一下:

       首先,被acegi无数个类和接口搞昏了头,以为此乃外星产物而不遵照安全性和权限验证的基本原理。其实权限验证无非就是这样一个套路,acegi也不可能例外的:所谓安全管理,就是用户在做一件事时(用户可能是真正的登陆用户,也可能是一个程序,一个另外的service,一个模块等等等等,acegi的概念就是principle;做一件事可能是访问一个web url,也可能是调用一个函数,也可能是访问资源文件等等等等,都是广义的),我们要判断这个用户有没有权限。怎么判断呢,无非就是要确定:a.用户是谁(Authentication),用户有哪些权限(Authorization),和b.用户要做的事情需要什么权限。然后判断一下ab是不是符合,如果符合就正常操作,如果不符合就不允许操作。这样一个流程,一一和acegi中的无数接口和类找对应关系,从这个角度在看一下《Professional Java Development with the Spring Framework》的第十章第二节中的类图和介绍,一下子就豁然开朗啊~

       另外一个,很龌龊的思维定式,就是“ROLE”。因为在应用层安全逻辑设计中,我们常常用Role来表示角色,用permission或者privilege来表示权限(权限点),所以在看到acegi中很多地方提到“ROLE”的时候下意识的就会认为这是说角色,那么潜在的认识就是认为这个角色下有一些权限点。在这个思维惯性下去看acegi framework的介绍,就会越看越晕,直至死掉……幸亏偶终于在某一天意识到在acegi中提到的ROLE其实是对应应用逻辑中的权限点(permission或者privilege),而应用逻辑中的Role(角色)的概念在acegi中根本不存在的,正如开始提到的acegi framework并不通过角色和权限来管理安全框架。如果没有这个问题,估计又能节省不少时间吧。

       基于这样的原理,我们只要在spring配置文件中给每个业务逻辑bean加上安全拦截器,在拦截器的objectDefinitionSource属性中配置每个需要安全管理的逻辑bean的类和方法需要的权限点的名字(以“ROLE_”开头),这样在应用层调用这个应用逻辑方法的时候,拦截器就会起作用,由acegi框架的AccessDecisionManager来进行操作,流程正如首先中提到的,在ContextHolder中取到principleAuthentication对象,然后从authentication中取到GrantedAuthorities,这就代表了用户有哪些权限;同时AccessDecisionManager还会从ConfigAttributeDefinition中(归根结底就是安全拦截器的配置中)取到ConfigAttribute(用户执行的操作需要什么权限);然后AccessDecisionManager就会调用AccessDecisionVoter(我实际用了它的一个实现,叫RoleVoter)的vote方法,来判断用户拥有的权限和要执行的操作需要的权限是不是有吻合,如果吻合的话拦截器就继续操作用户实际的MethodInvocation,如果不吻合就抛出PemissionDeniedException(当然这里面还有很多复杂的配置,如有多个Voter时最后的结构怎么判断,其他AccessDecisionVoter实现甚至非Voter实现等等的一系列配置)。

       明白了原理就可以干活了,成功的弄了个demo,然后在设置和不设置拦截器的时候调用,最后发现一切正常,开心啊。可是仔细一想,还不对啊,现在抛出的异常还是根本没有authentication时操作的异常,并不是用户已经通过了authentication而在authorization时发现权限不够抛出的异常。这才发现忽略了一个问题,前面一直玩的都是authorization的东西,而根本没有进入authentication的步骤呢。昏啊!经过这么一番折腾,偶已经濒临死亡了……,多亏在这时候偶找到了一个acegi达人:kapok_fly,在此要严重感谢,在他提供的帮助下,终于弄明白了acegi authentication的入口,在web.xml理配置了一个FilterToBeanProxyspring里一系列的filter之后,authentication也搞定了。当然了,这只是一个demo,用了acegi的默认的AuntenticationDao接口的实现JdbcDaoImp,经跟踪,它利用datasource里的usersauthorities两张表来组织了一个authentication对像(里面包含了用户都有哪些权限的信息)。在实际应用中,我们只要自己给出一个AuthenticationDao的实现,从自己的db中组织一个authentication对象就可以了。

Web.xml里需要加入的配置:

<context-param>

       <param-name>contextConfigLocation</param-name>

       <param-value>/WEB-INF/applicationContext.xml</param-value>

</context-param>

    <filter>

       <filter-name>Acegi Filter Chain Proxy</filter-name>

       <filter-class>

           net.sf.acegisecurity.util.FilterToBeanProxy

       </filter-class>

       <init-param>

           <param-name>targetClass</param-name>

           <param-value>

              net.sf.acegisecurity.util.FilterChainProxy

           </param-value>

       </init-param>

    </filter>

    <filter-mapping>

       <filter-name>Acegi Filter Chain Proxy</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

    <listener>

       <listener-class>

           org.springframework.web.context.ContextLoaderListener

       </listener-class>

    </listener>

在我的应用里,applicationContext.xml引用了spring-data.xmlsrping-struts-action.xml,等等其他部分,其中acege相关的东西放到了spring-security.xml里,内容如下:

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

    <!-- ======================== FILTER CHAIN ======================= -->

 

    <!--  if you wish to use channel security, add "channelProcessingFilter," in front

       of "httpSessionContextIntegrationFilter" in the list below -->

    <bean id="filterChainProxy"

       class="net.sf.acegisecurity.util.FilterChainProxy">

       <property name="filterInvocationDefinitionSource">

           <value>

              CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

              PATTERN_TYPE_APACHE_ANT

               /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter

           </value>

       </property>

    </bean>

    <bean id="httpSessionContextIntegrationFilter"

       class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter">

       <property name="context">

           <value>

              net.sf.acegisecurity.context.security.SecureContextImpl

           </value>

       </property>

    </bean>

    <bean id="authenticationProcessingFilter"

       class="test.security.TestAuthenticationProcessingFilter">

       <property name="authenticationManager">

           <ref bean="authenticationManager" />

       </property>

       <property name="authenticationFailureUrl">

           <value>/test/login.jsp?login_error=1</value>

       </property>

       <property name="defaultTargetUrl">

           <value>/</value>

       </property>

       <property name="filterProcessesUrl">

           <value>/test/j_acegi_security_check</value>

       </property>

       <property name="rememberMeServices">

           <ref local="rememberMeServices" />

       </property>

    </bean>

    <bean id="anonymousProcessingFilter"

       class="net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter">

       <property name="key">

           <value>foobar</value>

       </property>

       <property name="userAttribute">

           <value>anonymousUser,ROLE_ANONYMOUS</value>

       </property>

    </bean>

<bean id="rememberMeProcessingFilter"

       class="net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter">

       <property name="rememberMeServices">

           <ref local="rememberMeServices" />

       </property>

</bean>

    <bean id="securityEnforcementFilter"

       class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">

       <property name="filterSecurityInterceptor">

           <ref local="filterInvocationInterceptor" />

       </property>

       <property name="authenticationEntryPoint">

           <ref local="authenticationProcessingFilterEntryPoint" />

       </property>

    </bean>

 

    <bean id="authenticationProcessingFilterEntryPoint"

       class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">

       <property name="loginFormUrl">

           <value>/test/login.jsp</value>

       </property>

       <property name="forceHttps">

           <value>false</value>

       </property>

    </bean>

 

    <bean id="rememberMeServices"

       class="net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">

       <property name="authenticationDao">

           <ref local="jdbcDaoImpl" />

       </property>

       <property name="key">

           <value>springRocks</value>

       </property>

    </bean>

 

    <bean id="rememberMeAuthenticationProvider"

       class="net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">

       <property name="key">

           <value>springRocks</value>

       </property>

    </bean>

<!-- 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="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">

       <property name="authenticationManager">

           <ref bean="authenticationManager" />

       </property>

       <property name="accessDecisionManager">

           <ref local="businessAccessDecisionManager" />

       </property>

       <property name="objectDefinitionSource">

           <value>

              CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

              PATTERN_TYPE_APACHE_ANT /test/index.jsp*=ROLE_USER

              /test/login.jsp*=ROLE_ANONYMOUS /**=ROLE_USER

           </value>

       </property>

    </bean>

    <bean id="autoproxy"

       class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

    <bean id="methodSecurityAdvisor"

        class="net.sf.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"

       autowire="constructor" />  

    <bean id="authenticationManager"

       class="net.sf.acegisecurity.providers.ProviderManager">

       <property name="providers">

           <list>

              <ref local="daoAuthenticationProvider" />

              <ref local="anonymousAuthenticationProvider" />

              <ref local="rememberMeAuthenticationProvider" />

           </list>

       </property>

    </bean>

    <bean id="anonymousAuthenticationProvider"

       class="net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">

       <property name="key">

           <value>foobar</value>

       </property>

    </bean>

 

    <bean id="daoAuthenticationProvider"

       class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">

       <property name="authenticationDao">

           <ref local="jdbcDaoImpl" />

       </property>

    </bean>

 

    <bean id="jdbcDaoImpl"

       class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">

       <property name="dataSource">

           <ref bean="dataSource" />

       </property>

    </bean>

<bean id="logicBeanSecurity"

       class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">

       <property name="authenticationManager">

           <ref bean="authenticationManager" />

       </property>

       <property name="accessDecisionManager">

           <ref local="businessAccessDecisionManager" />

       </property>

       <property name="objectDefinitionSource">

           <value>

              test.logic.EmployeeManager.getAllEmployee=ROLE_USER

           </value>

       </property>

    </bean>

    <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter" />

    <bean id="businessAccessDecisionManager"

       class="net.sf.acegisecurity.vote.AffirmativeBased">

       <property name="allowIfAllAbstainDecisions">

           <value>false</value>

       </property>

       <property name="decisionVoters">

           <list>

              <ref bean="roleVoter" />

           </list>

       </property>

    </bean>

</beans>

        配置中指明了,用户如果还没有进行autentication操作,就会自动转向/test/login.jsp,这个页面提交到的action/test/j_acegi_security_checkacegi在这里调用AuthenticationDao进行authentication,然后把对象存入ContextHolder供以后拦截器使用。在拦截器logicBeanSecurity定义中看到test.logic.EmployeeManager.getAllEmployee=ROLE_USER,这里可以分多行写很多很多个,为每个方法分配一个权限点的名字(我们用RoleVoter的时候这个名字必须以“ROLE_”开头,这没什么,只要我们自己实现的AuthenticationDao在给用户添加权限名字的时候做成这样就可以了,只要我们自己清楚,这里的“ROLE”是代表应用中的权限点而不是角色)。

   业务逻辑bean添加拦截器的方法和添加事务拦截器的方法是一样的:

    <bean id="EmployeeManagerTarget"

       class="test.logic.EmployeeManager">

       <property name="dao">

           <ref bean="baseDao" />

       </property>

    </bean>

    <bean id="EmployeeManager"

       class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target">

           <ref local="EmployeeManagerTarget" />

       </property>

       <property name="interceptorNames">

           <list>

              <value>logicBeanSecurity</value>

              <value>transactionInterceptor</value>

           </list>

       </property>

    </bean>

友情提示一: <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"> <property name="rolePrefix"><value/></property> </bean> 这样就可以改你自己的前缀了,而不局限于ROLE

友情提示二:有空你可以跟到代码里面去研究一下PathBasedFilterInvocationDefinitionMap ,理论上应该可以实现URLROLE之间的定义直接从数据库读取,一直没时间做测试。

友情提示二续:就是<property name="objectDefinitionSource">这一行,理论上应该可以改成<bean ref="">这个beanPathBasedFilterInvocationDefinitionMap 扩张,应该可以实现DB load功能。Good Luck

 

  评论这张
 
阅读(371)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017