zoukankan      html  css  js  c++  java
  • 使用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

  • 相关阅读:
    DDD(Domain Driven Design) 架构设计
    兴哥:美团下一个十年,要让新一批管理者成长起来
    Java设计模式-Builder构造者模式
    社会需要转型变革,自己更需要转型变革-屡败屡战
    Guava 源码分析之Cache的实现原理
    缓存算法(FIFO 、LRU、LFU三种算法的区别)
    Guava---缓存之LRU算法
    Boost.Asio 网络编程([译]Boost.Asio基本原理)
    浅谈 Boost.Asio 的多线程模型
    boost.asio系列——io_service
  • 原文地址:https://www.cnblogs.com/yanduanduan/p/5208893.html
Copyright © 2011-2022 走看看