`

在spring security3上实现验证码

阅读更多

关于验证码的实现

验证码的例子现在多如牛毛,大家google一下就有很多,通常的做法是在session中生成一个随机串,再由用户提交到server端去验证。因为最近在看SS3,所以这里主要想讲验证码与SS3的结合。

验证码与SS3结合的实现方案

方案一:自己写个filter

严格来讲,这不是与SS3结合,而只是在项目中实现一个filter,然后将其拦截次序放在SS3的filter前面即可。这样做的好处是简单且通 用,以后不玩SS3了,也可以使用。缺点也很明显,那就是在SS3中配置的一应事物都要重新配置,例如拦截的URL、失败的URL、报错的资源文件、异常 后的重定向设置等等。

方案二:定制一个新的SS3的filter

通过在<http>中声明一个<custom-filter>,设置其"before"属性,让它 在"FORM_LOGIN_FILTER"前作拦截即可。这样做看上去更集中、直观,对SS3的<http>元素的默认设置改变也很小,但是 和方法一相似,还是要配置很多事物,写很多的基础代码。

方案三:扩展UsernamePasswordAuthenticationFilter

改方案通过继承UsernamePasswordAuthenticationFilter,并重载attemptAuthentication方 法,在其中增加校验验证码的逻辑。其优点是省去了编写上文中说的基础代码,相关的URL、资源文件的配置只要再此filter中配置一次即可;其缺点就是 将其插入到已有的NamingSpace声明的拦截器链中非常麻烦。我们要理解NamingSpace的配置信息、理解拦截器的顺序、别名、作用,以及要 深入SS3的源码,看UsernamePasswordAuthenticationFilter的源码,了解它及其父类中有哪些属性是必须配置或我们需 要重新配置的。

虽然这种方法烦了一点,但是我最终还是选择了此方法。因为其难度是建立于理解SS3之上的,而其好处也显而易见。

实现步骤

1.自定义UsernamePasswordAuthenticationFilter

这一步还是很简单的,我们声明一个类:ValidateCodeUsernamePasswordAuthenticationFilter,它继 承UsernamePasswordAuthenticationFilter,并重载attemptAuthentication方法,增加校验验证码 码的逻辑。这里要稍稍抗议一下SS3的作者们,那个"postOnly"属性为什么不写个读方法呢……以前在扩展Acegi的时候也遇到过类似的情况某个 私有成员变量没有读方法而被迫重写了大段的代码,虽然很多事copy的……

来看一下attemAuthentication的代码片段:

Java代码
  1. if  (!isAllowEmptyValidateCode())  
  2.     checkValidateCode(request);  
  3. return   this .getAuthenticationManager().authenticate(authRequest);  
if (!isAllowEmptyValidateCode())    checkValidateCode(request);   return this.getAuthenticationManager().authenticate(authRequest);

 

checkValidateCode也很简单:

Java代码
  1. protected   void  checkValidateCode(HttpServletRequest request) {  
  2.     String sessionValidateCode = obtainSessionValidateCode(request);  
  3.     String validateCodeParameter = obtainValidateCodeParameter(request);  
  4.     if  (StringUtils.isEmpty(validateCodeParameter) || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {  
  5.         throw   new  AuthenticationServiceException(messages.getMessage( "validateCode.notEquals" ));  
  6.     }  
  7. }  
protected void checkValidateCode(HttpServletRequest request) {   String sessionValidateCode = obtainSessionValidateCode(request);   String validateCodeParameter = obtainValidateCodeParameter(request);   if (StringUtils.isEmpty(validateCodeParameter) || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {    throw new AuthenticationServiceException(messages.getMessage("validateCode.notEquals"));   }  }

 

这里有几点想说明一下:

  1. 这里使用messages,符合上文中我们说直接利用SS3中的基础构建。
  2. 当校验出错时,抛出AuthenticationServiceException异常,这里其实大家可以自定义一个异常类,继承AuthenticationException即可。抛出这个异常后,父类中的代码会为我们处理这个异常,让我们享受一下继承的优势。
  3. 之所以在Filter中校验验证码是因为之类有我们需要的web接口,实现更加方便。

 2.配置自定义的UsernamePasswordAuthenticationFilter

替换<form-login>

原有在<http>中的<form-login>肯定是不能在用了,我们使用一个<custom-filter>来替换:

Java代码
  1. <custom-filter ref= "validateCodeAuthenticationFilter"  position= "FORM_LOGIN_FILTER"  />  
<custom-filter ref="validateCodeAuthenticationFilter" position="FORM_LOGIN_FILTER" />

 position表示我们替换了原来别名"FORM_LOGIN_FILTER"所标示的 类:UsernamePasswordAuthenticationFilter。但事情并非这么简单,通过阅读SS3的手册2.3、5.4节,我们得知 还需要一个AuthenticationEntryPoint:

Xml代码
  1. < beans:bean   id = "authenticationProcessingFilterEntryPoint"   
  2.     class = "org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint" >   
  3.     < beans:property   name = "loginFormUrl"   value = "/login" > </ beans:property >   
  4. </ beans:bean >   
<beans:bean id="authenticationProcessingFilterEntryPoint"   class="org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint">   <beans:property name="loginFormUrl" value="/login"></beans:property>  </beans:bean>

相应的,<http>也需要做点修改:

Xml代码
  1. < http   use-expressions = "true"   entry-point-ref = "authenticationProcessingFilterEntryPoint" >    
<http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint"> 

 配置ValidateCodeUsernamePasswordAuthenticationFilter

配置ValidateCodeUsernamePasswordAuthenticationFilter时,我们需要将所需的属性配置完全,包括 认证成功、失败的处理器。这里多出来的配置,主要是在bean上,而bean中需要的属性,如认证过滤URL、认证成功URL、认证失败URL 在<form-login>中也是需要配置的,所以我们的工作并不多:

Xml代码
  1. < beans:bean   id = "validateCodeAuthenticationFilter"   
  2.     class = "com.cloudframework.extend.spring.security.web.authentication.ValidateCodeUsernamePasswordAuthenticationFilter" >   
  3.     < beans:property   name = "filterProcessesUrl"   value = "/logon" > </ beans:property >   
  4.     < beans:property   name = "authenticationSuccessHandler"   
  5.         ref = "loginLogAuthenticationSuccessHandler" > </ beans:property >   
  6.     < beans:property   name = "authenticationFailureHandler"   
  7.         ref = "simpleUrlAuthenticationFailureHandler" > </ beans:property >   
  8.     < beans:property   name = "authenticationManager"   ref = "authenticationManager" > </ beans:property >   
  9. </ beans:bean >   
  10. < beans:bean   id = "loginLogAuthenticationSuccessHandler"   
  11.     class = "org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler" >   
  12.     < beans:property   name = "defaultTargetUrl"   value = "/main" > </ beans:property >   
  13. </ beans:bean >   
  14. < beans:bean   id = "simpleUrlAuthenticationFailureHandler"   
  15.     class = "org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" >   
  16.     < beans:property   name = "defaultFailureUrl"   value = "/login" > </ beans:property >   
  17. </ beans:bean >   
<beans:bean id="validateCodeAuthenticationFilter"   class="com.cloudframework.extend.spring.security.web.authentication.ValidateCodeUsernamePasswordAuthenticationFilter">   <beans:property name="filterProcessesUrl" value="/logon"></beans:property>   <beans:property name="authenticationSuccessHandler"    ref="loginLogAuthenticationSuccessHandler"></beans:property>   <beans:property name="authenticationFailureHandler"    ref="simpleUrlAuthenticationFailureHandler"></beans:property>   <beans:property name="authenticationManager" ref="authenticationManager"></beans:property>  </beans:bean>  <beans:bean id="loginLogAuthenticationSuccessHandler"   class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">   <beans:property name="defaultTargetUrl" value="/main"></beans:property>  </beans:bean>  <beans:bean id="simpleUrlAuthenticationFailureHandler"   class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">   <beans:property name="defaultFailureUrl" value="/login"></beans:property>  </beans:bean>

 注意一下这里的"authenticationManager"属性,在NamingSpace的默认配置里,我们不需要特别指定这个属 性,SS3会为我们找到<authentication-manager>。此时,我们需要给<authentication- manager>配置一个别名:

Xml代码
  1. < authentication-manager   alias = "authenticationManager" >   
<authentication-manager alias="authenticationManager">

做点优化

从配置文件中,我们看到登录链接被引用了多次,我们可以将其写在一个.properties文件中,并在xml中引用。

 

 到此,一切事物准备就绪,验证码可以正常工作了。

后记

我从08年起,利用Acegi1.0.6构建公司内多系统见的认证、授权功能,当时没有NamingSpace,有的只是一个针对请求的拦截器链和 Spring beans,虽然繁琐,但是清晰、明了。顺着这个链走下去,让你了解什么认证、授权工作的步骤及其内因,例如著名的投票策略。多年以后再回首,曾经的 Acegi,摇身一变成了Spring Security,丰富了很多的功能,文档也做了很多的改进,但是也像他的亲爹Spring一样,穿上了一件又一件的花衣服,NamingSpace是很 酷,但也增加了一个初学者了解其内里的难度。

分享到:
评论
1 楼 Welocme鹏友 2013-12-21  
<!-- 前台认证管理器 -->
    <authentication-manager alias="userAuthManager" >
        <authentication-provider user-service-ref="frontUserDetailsServiceImpl" />
    </authentication-manager>
    <!--后台认证管理器 -->
    <authentication-manager alias="adminAuthManager">
        <authentication-provider user-service-ref="adminDetailsServiceImpl" />
    </authentication-manager>


楼主,我遇到了一个问题。我有两张表User和Admin,因此我得写两个UserDetailServiceImpl(FrontUserDetailsServiceImpl和AdminDetailsServiceImpl),按照楼主的方法我也写了两个过滤器(UserAuthenticationFilter.java和AdminAuthenticationFilter.java)分别用于前台和后台的用户验证,过滤器引用了各自的认证管理器authenticationManager(上述代码),但是当我运行起来,发现后台管理员可以登陆成功,前台用户却无法登陆成功,断点之后发现,原来(UserAuthenticationFilter的attemptAuthentication方法每次验证完用户名和密码,return this.getAuthenticationManager().authenticate(authRequest)调用的是AdminDetailsServiceImpl中的loadUserByUsername方法,我把配置文件中的两个认证管理器调换一下位置,让“后台认证管理器”在前,另一个在后,这次是前天用户可以登陆成功,后台管理员无法登陆,感觉是<authentication-manager>只能配置一个,配置多个的话,后一个会把前面配置的都覆盖了,不知道是不是这样子?想了很久不知道如何解决,不知道楼主有什么解决方法?

相关推荐

Global site tag (gtag.js) - Google Analytics