验证码的例子现在多如牛毛,大家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代码- if (!isAllowEmptyValidateCode())
- checkValidateCode(request);
- return this.getAuthenticationManager().authenticate(authRequest);
checkValidateCode也很简单:
Java代码- 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"));
- }
- }
这里有几点想说明一下:
- 这里使用messages,符合上文中我们说直接利用SS3中的基础构建。
- 当校验出错时,抛出AuthenticationServiceException异常,这里其实大家可以自定义一个异常类,继承AuthenticationException即可。抛出这个异常后,父类中的代码会为我们处理这个异常,让我们享受一下继承的优势。
- 之所以在Filter中校验验证码是因为之类有我们需要的web接口,实现更加方便。
原有在<http>中的<form-login>肯定是不能在用了,我们使用一个<custom-filter>来替换:
Java代码- <custom-filter ref="validateCodeAuthenticationFilter" position="FORM_LOGIN_FILTER" />
position表示我们替换了原来别名"FORM_LOGIN_FILTER"所标示的 类:UsernamePasswordAuthenticationFilter。但事情并非这么简单,通过阅读SS3的手册2.3、5.4节,我们得知 还需要一个AuthenticationEntryPoint:
Xml代码- <beans:bean id="authenticationProcessingFilterEntryPoint"
- class="org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint">
- <beans:property name="loginFormUrl" value="/login"></beans:property>
- </beans:bean>
相应的,<http>也需要做点修改:
Xml代码- <http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
配置ValidateCodeUsernamePasswordAuthenticationFilter时,我们需要将所需的属性配置完全,包括 认证成功、失败的处理器。这里多出来的配置,主要是在bean上,而bean中需要的属性,如认证过滤URL、认证成功URL、认证失败URL 在<form-login>中也是需要配置的,所以我们的工作并不多:
Xml代码- <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代码- <authentication-manager alias="authenticationManager">
从配置文件中,我们看到登录链接被引用了多次,我们可以将其写在一个.properties文件中,并在xml中引用。
到此,一切事物准备就绪,验证码可以正常工作了。
后记我从08年起,利用Acegi1.0.6构建公司内多系统见的认证、授权功能,当时没有NamingSpace,有的只是一个针对请求的拦截器链和 Spring beans,虽然繁琐,但是清晰、明了。顺着这个链走下去,让你了解什么认证、授权工作的步骤及其内因,例如著名的投票策略。多年以后再回首,曾经的 Acegi,摇身一变成了Spring Security,丰富了很多的功能,文档也做了很多的改进,但是也像他的亲爹Spring一样,穿上了一件又一件的花衣服,NamingSpace是很 酷,但也增加了一个初学者了解其内里的难度。
- code.rar (2.4 KB)
- 下载次数: 8