使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM