使用SpringSecurity3用户验证(异常信息,验证码)

1. 自定义user-service后,封装自定义异常信息返回

通常情况下,抛UsernameNotFoundException异常信息是捕捉不了,跟踪源码后发现

Java代码  收藏代码
  1. try {  
  2.     user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  
  3. catch (UsernameNotFoundException notFound) {  
  4.     logger.debug("User '" + username + "' not found");  
  5.   
  6.     if (hideUserNotFoundExceptions) {  
  7.         throw new BadCredentialsException(messages.getMessage(  
  8.                 "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));  
  9.     } else {  
  10.         throw notFound;  
  11.     }  
  12. }  

而默认情况下,hideUserNotFoundExceptions为true。所以就会导致明明抛UsernameNotFoundException,但前台还是只能捕获Bad credentials的问题。

解决办法我们可以直接覆盖 org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider 的类,然后修改hideUserNotFoundExceptions为false。

当然,这样的解决办法并不好。所以,我们还是走正规的途径,自定义 org.springframework.security.authentication.dao.DaoAuthenticationProvider 来替换默认的即可,即修改配置文件并定义provider,这就是IoC的伟大之处。

原来authentication-manager中简单的定义user-service-ref

Xml代码  收藏代码
  1. <authentication-manager alias="authenticationManager">  
  2.     <authentication-provider user-service-ref="myUserDetailsService">  
  3.         <!-- 密码加密方式  -->  
  4.         <password-encoder hash="md5" />  
  5.     </authentication-provider>  
  6. </authentication-manager>  
 

现在修改如下:

Xml代码  收藏代码
  1. <authentication-manager alias="authenticationManager">  
  2.     <authentication-provider ref="authenticationProvider" />  
  3. </authentication-manager>  
  4.   
  5. <b:bean id="authenticationProvider"  
  6.     class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">  
  7.     <b:property name="userDetailsService" ref="myUserDetailsService" />  
  8.     <b:property name="hideUserNotFoundExceptions" value="false" />  
  9.     <b:property name="passwordEncoder" ref="passwordEncoder"></b:property>  
  10. </b:bean>  
  11.   
  12. <b:bean  
  13.     class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"  
  14.     id="passwordEncoder"></b:bean>  

这样修改后,在登录页面获取的异常已经是自己抛出去的UsernameNotFoundException了。

(注:这里保留了md5加密方式,但是原始的加密,没加salt,之后会继续修改为安全性高一些的md5+salt加密。现在这世道普通的md5加密和明文没多大区别。)

2. 国际化资源i18n信息

若想封装国际化资源信息到页面(不想打硬编码信息到代码内),又不想自己构造Properties对象的话,可以参考SpringSecurity3中的获取资源文件方法。(也是看源码的时候学习到的)

在SpringSecurity3中的message都是通过这样的方式得到的:

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

 
通过提供的静态方法,我们很方便的得到国际化资源信息。但无奈SpringSecurityMessageSource硬编码写死了只是获取org.springframework.security.messages的资源文件(英文信息)。如下:
 
Java代码  收藏代码
  1. public SpringSecurityMessageSource() {  
  2.     setBasename("org.springframework.security.messages");  
  3. }  
 
 
通常情况下,这个并不符合我们的使用,并且很多情况下,使用SpringSecurity3自定义抛出的异常信息的话,也会出现不符合语言习惯的信息。
 
所以,这里是建议覆盖org.springframework.security.core.SpringSecurityMessageSource类,并指定获取应用中的默认国际化资源文件。
 
不过,你还是不想覆盖别人的类的话,也还可以自己模仿SpringSecurityMessageSource编写自己的获取MessageSourceAccessor的类,例如我就是这么做....
 
Java代码  收藏代码
  1. public class SpringMessageSource extends ResourceBundleMessageSource {  
  2.     // ~ Constructors  
  3.     // ===================================================================================================  
  4.   
  5.     public SpringMessageSource() {  
  6.         setBasename("com.foo.resources.messages_zh_CN");  
  7.     }  
  8.   
  9.     // ~ Methods  
  10.     // ========================================================================================================  
  11.   
  12.     public static MessageSourceAccessor getAccessor() {  
  13.         return new MessageSourceAccessor(new SpringMessageSource());  
  14.     }  
  15. }  
 
 
这样,我们就可以在自定义的userDetailsService类中,像SpringSecurity3那样方便的使用国际化资源文件了。
 
如:
Java代码  收藏代码
  1.     private MessageSourceAccessor messages = SpringMessageSource.getAccessor();  
  2.   
  3. ....  
  4.   
  5.     public UserDetails loadUserByUsername(String username)  
  6.             throws UsernameNotFoundException, DataAccessException {  
  7.         if (StringUtils.isBlank(username)) {  
  8.             throw new UsernameNotFoundException(  
  9.                     messages.getMessage("PasswordComparisonAuthenticator.badCredentials"),  
  10.                     username);  
  11.         }  
  12.   
  13. ...  
  14.   
  15. }  
 
3.添加验证码
 
在实际应用中,其实验证码是少不了的,不然很容易就被暴力破解了。添加验证码起码也可以增加一点安全性,而且添加验证码也比较简单。
 
添加自定义UsernamePasswordAuthenticationFilter,在验证username和password之前,我们加入验证码的判定。
 
在spring-security配置文件中的<http>代码块中添加
 
Xml代码  收藏代码
  1. <custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />  
 
然后就是在beans内添加定义validateCodeAuthenticationFilter的bean代码
 
 
Xml代码  收藏代码
  1. <b:bean id="validateCodeAuthenticationFilter"  
  2.     class="com.foo.security.ValidateCodeAuthenticationFilter">  
  3.     <b:property name="postOnly" value="false"></b:property>  
  4.     <b:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></b:property>  
  5.     <b:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></b:property>  
  6.     <b:property name="authenticationManager" ref="authenticationManager"></b:property>  
  7. </b:bean>  
  8.   
  9. <b:bean id="loginLogAuthenticationSuccessHandler"  
  10.     class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">  
  11.     <b:property name="defaultTargetUrl" value="/index.do"></b:property>  
  12. </b:bean>  
  13. <b:bean id="simpleUrlAuthenticationFailureHandler"  
  14.     class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
  15.     <b:property name="defaultFailureUrl" value="/login.jsp?login_error=1"></b:property>  
  16. </b:bean>  
 
最后是ValidateCodeAuthenticationFilter的源码:
 
Java代码  收藏代码
  1. public class ValidateCodeAuthenticationFilter extends  
  2.         UsernamePasswordAuthenticationFilter {  
  3.   
  4.     private boolean postOnly = true;  
  5.     private boolean allowEmptyValidateCode = false;  
  6.     private String sessionvalidateCodeField = DEFAULT_SESSION_VALIDATE_CODE_FIELD;  
  7.     private String validateCodeParameter = DEFAULT_VALIDATE_CODE_PARAMETER;  
  8.     public static final String DEFAULT_SESSION_VALIDATE_CODE_FIELD = "validateCode";  
  9.     public static final String DEFAULT_VALIDATE_CODE_PARAMETER = "validateCode";  
  10.     public static final String VALIDATE_CODE_FAILED_MSG_KEY = "validateCode.notEquals";  
  11.   
  12.     @Override  
  13.     public Authentication attemptAuthentication(HttpServletRequest request,  
  14.             HttpServletResponse response) throws AuthenticationException {  
  15.         if (postOnly && !request.getMethod().equals("POST")) {  
  16.             throw new AuthenticationServiceException(  
  17.                     "Authentication method not supported: "  
  18.                             + request.getMethod());  
  19.         }  
  20.   
  21.         String username = StringUtils.trimToEmpty(obtainUsername(request));  
  22.         String password = obtainPassword(request);  
  23.         if (password == null) {  
  24.             password = StringUtils.EMPTY;  
  25.         }  
  26.   
  27.         UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(  
  28.                 username, password);  
  29.   
  30.         // Place the last username attempted into HttpSession for views  
  31.         HttpSession session = request.getSession(false);  
  32.   
  33.         if (session != null || getAllowSessionCreation()) {  
  34.             request.getSession().setAttribute(  
  35.                     SPRING_SECURITY_LAST_USERNAME_KEY,  
  36.                     TextEscapeUtils.escapeEntities(username));  
  37.         }  
  38.   
  39.         // Allow subclasses to set the "details" property  
  40.         setDetails(request, authRequest);  
  41.         // check validate code  
  42.         if (!isAllowEmptyValidateCode())  
  43.             checkValidateCode(request);  
  44.         return this.getAuthenticationManager().authenticate(authRequest);  
  45.     }  
  46.   
  47.     /** 
  48.      *  
  49.      * <li>比较session中的验证码和用户输入的验证码是否相等</li> 
  50.      *  
  51.      */  
  52.     protected void checkValidateCode(HttpServletRequest request) {  
  53.         String sessionValidateCode = obtainSessionValidateCode(request);  
  54.         String validateCodeParameter = obtainValidateCodeParameter(request);  
  55.         if (StringUtils.isEmpty(validateCodeParameter)  
  56.                 || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {  
  57.             throw new AuthenticationServiceException(  
  58.                     messages.getMessage(VALIDATE_CODE_FAILED_MSG_KEY));  
  59.         }  
  60.     }  
  61.   
  62.     private String obtainValidateCodeParameter(HttpServletRequest request) {  
  63.         return request.getParameter(validateCodeParameter);  
  64.     }  
  65.   
  66.     protected String obtainSessionValidateCode(HttpServletRequest request) {  
  67.         Object obj = request.getSession()  
  68.                 .getAttribute(sessionvalidateCodeField);  
  69.         return null == obj ? "" : obj.toString();  
  70.     }  
  71.   
  72.     public boolean isPostOnly() {  
  73.         return postOnly;  
  74.     }  
  75.   
  76.     @Override  
  77.     public void setPostOnly(boolean postOnly) {  
  78.         this.postOnly = postOnly;  
  79.     }  
  80.   
  81.     public String getValidateCodeName() {  
  82.         return sessionvalidateCodeField;  
  83.     }  
  84.   
  85.     public void setValidateCodeName(String validateCodeName) {  
  86.         this.sessionvalidateCodeField = validateCodeName;  
  87.     }  
  88.   
  89.     public boolean isAllowEmptyValidateCode() {  
  90.         return allowEmptyValidateCode;  
  91.     }  
  92.   
  93.     public void setAllowEmptyValidateCode(boolean allowEmptyValidateCode) {  
  94.         this.allowEmptyValidateCode = allowEmptyValidateCode;  
  95.     }  
  96.   
  97. }  
 
附件中有生成CODE图片的JSP(相对比较简单的,但基本可以满足应用),还有文章中用到的一些关键配置文件与源码。
 
生成验证码的jsp页面调用时直接<img src="./validateCode.jsp"  />即可,但刷新时,记得在URL上增加随机数的参数,不然会有缓存导致刷新失败。
 
 
添加验证码部分有参考:http://www.iteye.com/topic/720867

原文地址:https://www.cnblogs.com/yanduanduan/p/5208893.html