Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 426|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-6-7 11:36:30 | 显示全部楼层 |阅读模式

    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

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-2 12:43 , Processed in 0.083074 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表