论坛首页 Java企业应用论坛

在spring security3上实现验证码

浏览 16586 次
精华帖 (0) :: 良好帖 (4) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-07-25  

关于验证码的实现

验证码的例子现在多如牛毛,大家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的代码片段:

		if (!isAllowEmptyValidateCode())
			checkValidateCode(request);
		return this.getAuthenticationManager().authenticate(authRequest);

 

checkValidateCode也很简单:

	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>来替换:

		<custom-filter ref="validateCodeAuthenticationFilter" position="FORM_LOGIN_FILTER" />

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

	<beans:bean id="authenticationProcessingFilterEntryPoint"
		class="org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint">
		<beans:property name="loginFormUrl" value="/login"></beans:property>
	</beans:bean>

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

<http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint"> 

 配置ValidateCodeUsernamePasswordAuthenticationFilter

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

	<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>配置一个别名:

<authentication-manager alias="authenticationManager">

做点优化

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

 

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

后记

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

   发表时间:2010-07-25  
我认为Spring Security的重大缺陷就是它的Namespace定的过于死板。

比如说,在其文档中有这么一段:

引用
注意,你不能替换那些<http>元素自己使用而创建出的过滤器,比如SecurityContextPersistenceFilter, ExceptionTranslationFilter 或 FilterSecurityInterceptor。

如果你替换了一个命名空间的过滤器,而这个过滤器需要一个验证入口点(比如,认证过程是通过一个未通过验证的用户访问受保护资源的尝试来触发的),你将也需要添加一个自定义的入口点bean。


而实际上我们知道,FilterSecurityInterceptor这样重要的用于URL等资源校验的Filter,我们往往是要进行扩展的。

Spring是一个非常灵活的框架,而Spring Security却如此霸王。在一定程度上,这样的设计的确可以避免很多由于Filter次序配置的混乱造成的问题,然而却失去了灵活性。可谓是得不偿失。
0 请登录后投票
   发表时间:2010-07-25  
downpour 写道
我认为Spring Security的重大缺陷就是它的Namespace定的过于死板。

比如说,在其文档中有这么一段:

引用
注意,你不能替换那些<http>元素自己使用而创建出的过滤器,比如SecurityContextPersistenceFilter, ExceptionTranslationFilter 或 FilterSecurityInterceptor。

如果你替换了一个命名空间的过滤器,而这个过滤器需要一个验证入口点(比如,认证过程是通过一个未通过验证的用户访问受保护资源的尝试来触发的),你将也需要添加一个自定义的入口点bean。


而实际上我们知道,FilterSecurityInterceptor这样重要的用于URL等资源校验的Filter,我们往往是要进行扩展的。

Spring是一个非常灵活的框架,而Spring Security却如此霸王。在一定程度上,这样的设计的确可以避免很多由于Filter次序配置的混乱造成的问题,然而却失去了灵活性。可谓是得不偿失。

这段话的确是在文档中,不过你的中文文档在哪啊,我只在官网找到英文的,能给个链接不?

我的例子中已经给了“验证入口点”,觉得实现起来不是太麻烦。当我看到不能替换那段时,我也惊掉了下巴,不过我还没试过。

原来定制Acegi的拦截器链也没觉得太混乱,还是挺清晰的。现在看到命名空间整的这么花哨,让我怀疑是不是这波是不是想靠培训挣点钱啊……
0 请登录后投票
   发表时间:2010-07-26   最后修改:2010-07-26
downpour 写道
我认为Spring Security的重大缺陷就是它的Namespace定的过于死板。

......

Spring是一个非常灵活的框架,而Spring Security却如此霸王。在一定程度上,这样的设计的确可以避免很多由于Filter次序配置的混乱造成的问题,然而却失去了灵活性。可谓是得不偿失。


虽然想说:“不用namespace,直接用原来那种acegi的配置方式就行了。”但是这样说明显是在强词夺理。

言归正传,namespace不怎么灵活,如果在它上面进行扩展,除了user-details-service,authentication-provider那种已经留好的扩展点,要改其他的东西都必须看源代码,搞明白了里边的运行结构才能动刀子。从这点来看,spring security还很不成熟。

former 写道

原来定制Acegi的拦截器链也没觉得太混乱,还是挺清晰的。现在看到命名空间整的这么花哨,让我怀疑是不是这波是不是想靠培训挣点钱啊……


namespace最大的功劳是降低了新手的入门门槛,相比acegi需要几百行代码才能完成Hello world,namespace只需要10几行xml就ok了。花俏也是迫不得已的,过于复杂的配置让开发和维护都步履维艰,为啥不让修改几个默认的过滤?还不是因为acegi时代总是有人配置错误。

实际上,spring security除了支持namespace以外,像你这样的老手依然可以使用acegi那种超复杂的方式进行配置,不要导入springsecurity-config包就行了。
1 请登录后投票
   发表时间:2010-07-26  
xyz20003 写道
downpour 写道
我认为Spring Security的重大缺陷就是它的Namespace定的过于死板。

......

Spring是一个非常灵活的框架,而Spring Security却如此霸王。在一定程度上,这样的设计的确可以避免很多由于Filter次序配置的混乱造成的问题,然而却失去了灵活性。可谓是得不偿失。


虽然想说:“不用namespace,直接用原来那种acegi的配置方式就行了。”但是这样说明显是在强词夺理。

言归正传,namespace不怎么灵活,如果在它上面进行扩展,除了user-details-service,authentication-provider那种已经留好的扩展点,要改其他的东西都必须看源代码,搞明白了里边的运行结构才能动刀子。从这点来看,spring security还很不成熟。

former 写道

原来定制Acegi的拦截器链也没觉得太混乱,还是挺清晰的。现在看到命名空间整的这么花哨,让我怀疑是不是这波是不是想靠培训挣点钱啊……


namespace最大的功劳是降低了新手的入门门槛,相比acegi需要几百行代码才能完成Hello world,namespace只需要10几行xml就ok了。花俏也是迫不得已的,过于复杂的配置让开发和维护都步履维艰,为啥不让修改几个默认的过滤?还不是因为acegi时代总是有人配置错误。

实际上,spring security除了支持namespace以外,像你这样的老手依然可以使用acegi那种超复杂的方式进行配置,不要导入springsecurity-config包就行了。


非常中肯的评价。

我们需要看到的是Spring Security还处于发展的过程中。期待他变得越来越好。
0 请登录后投票
   发表时间:2010-07-26  
xyz20003 写道
downpour 写道
我认为Spring Security的重大缺陷就是它的Namespace定的过于死板。

......

Spring是一个非常灵活的框架,而Spring Security却如此霸王。在一定程度上,这样的设计的确可以避免很多由于Filter次序配置的混乱造成的问题,然而却失去了灵活性。可谓是得不偿失。


虽然想说:“不用namespace,直接用原来那种acegi的配置方式就行了。”但是这样说明显是在强词夺理。

言归正传,namespace不怎么灵活,如果在它上面进行扩展,除了user-details-service,authentication-provider那种已经留好的扩展点,要改其他的东西都必须看源代码,搞明白了里边的运行结构才能动刀子。从这点来看,spring security还很不成熟。

former 写道

原来定制Acegi的拦截器链也没觉得太混乱,还是挺清晰的。现在看到命名空间整的这么花哨,让我怀疑是不是这波是不是想靠培训挣点钱啊……


namespace最大的功劳是降低了新手的入门门槛,相比acegi需要几百行代码才能完成Hello world,namespace只需要10几行xml就ok了。花俏也是迫不得已的,过于复杂的配置让开发和维护都步履维艰,为啥不让修改几个默认的过滤?还不是因为acegi时代总是有人配置错误。

实际上,spring security除了支持namespace以外,像你这样的老手依然可以使用acegi那种超复杂的方式进行配置,不要导入springsecurity-config包就行了。




提供的命名空间的定制在一定程度上简化了acegi的配置,定制起来还是有点麻烦,为了使用3.0版本的security,源码阅读了一部分才搞明白!

0 请登录后投票
   发表时间:2010-07-26  
不过从我目前阅读文档和代码的情况来看,其对原有代码的改动并不是太大,没有很难上手的感觉。
毕竟事情还是那个事……
0 请登录后投票
   发表时间:2010-07-26  
轻轻地问一下,你这验证码错了,你怎么在前台给用户提示呢
0 请登录后投票
   发表时间:2010-07-26   最后修改:2010-07-26
wpfwupengfeiwpf 写道
轻轻地问一下,你这验证码错了,你怎么在前台给用户提示呢

easy,看下父类AbstractAuthenticationProcessingFilter里面的unsuccessfulAuthentication:

    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication request failed: " + failed.toString());
            logger.debug("Updated SecurityContextHolder to contain null Authentication");
            logger.debug("Delegating to authentication failure handler" + failureHandler);
        }

        HttpSession session = request.getSession(false);

        if (session != null || allowSessionCreation) {
            request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
        }

        rememberMeServices.loginFail(request, response);

        failureHandler.onAuthenticationFailure(request, response, failed);
    }

 

我们在校验验证码的时候抛出的异常被丢到了Session中,这样你可以在页面上拿到异常信息,辅助message的本地化处理或者不同的异常子类,你可以告诉用户他错在哪了。

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics