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

mac2022-06-30  17

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

 

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

 

 

Java代码   try {      user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);  } catch (UsernameNotFoundException notFound) {      logger.debug("User '" + username + "' not found");        if (hideUserNotFoundExceptions) {          throw new BadCredentialsException(messages.getMessage(                  "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));      } else {          throw notFound;      }  }  

 

 

而默认情况下,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代码   <authentication-manager alias="authenticationManager">      <authentication-provider user-service-ref="myUserDetailsService">          <!-- 密码加密方式  -->          <password-encoder hash="md5" />      </authentication-provider>  </authentication-manager>    

 

 

现在修改如下:

 

 

 

Xml代码   <authentication-manager alias="authenticationManager">      <authentication-provider ref="authenticationProvider" />  </authentication-manager>    <b:bean id="authenticationProvider"      class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">      <b:property name="userDetailsService" ref="myUserDetailsService" />      <b:property name="hideUserNotFoundExceptions" value="false" />      <b:property name="passwordEncoder" ref="passwordEncoder"></b:property>  </b:bean>    <b:bean      class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"      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代码   public SpringSecurityMessageSource() {      setBasename("org.springframework.security.messages");  }       通常情况下,这个并不符合我们的使用,并且很多情况下,使用SpringSecurity3自定义抛出的异常信息的话,也会出现不符合语言习惯的信息。   所以,这里是建议覆盖org.springframework.security.core.SpringSecurityMessageSource类,并指定获取应用中的默认国际化资源文件。   不过,你还是不想覆盖别人的类的话,也还可以自己模仿SpringSecurityMessageSource编写自己的获取MessageSourceAccessor的类,例如我就是这么做....   Java代码   public class SpringMessageSource extends ResourceBundleMessageSource {      // ~ Constructors      // ===================================================================================================        public SpringMessageSource() {          setBasename("com.foo.resources.messages_zh_CN");      }        // ~ Methods      // ========================================================================================================        public static MessageSourceAccessor getAccessor() {          return new MessageSourceAccessor(new SpringMessageSource());      }  }       这样,我们就可以在自定义的userDetailsService类中,像SpringSecurity3那样方便的使用国际化资源文件了。   如: Java代码       private MessageSourceAccessor messages = SpringMessageSource.getAccessor();    ....        public UserDetails loadUserByUsername(String username)              throws UsernameNotFoundException, DataAccessException {          if (StringUtils.isBlank(username)) {              throw new UsernameNotFoundException(                      messages.getMessage("PasswordComparisonAuthenticator.badCredentials"),                      username);          }    ...    }     3.添加验证码   在实际应用中,其实验证码是少不了的,不然很容易就被暴力破解了。添加验证码起码也可以增加一点安全性,而且添加验证码也比较简单。   添加自定义UsernamePasswordAuthenticationFilter,在验证username和password之前,我们加入验证码的判定。   在spring-security配置文件中的<http>代码块中添加   Xml代码   <custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />     然后就是在beans内添加定义validateCodeAuthenticationFilter的bean代码     Xml代码   <b:bean id="validateCodeAuthenticationFilter"      class="com.foo.security.ValidateCodeAuthenticationFilter">      <b:property name="postOnly" value="false"></b:property>      <b:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></b:property>      <b:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></b:property>      <b:property name="authenticationManager" ref="authenticationManager"></b:property>  </b:bean>    <b:bean id="loginLogAuthenticationSuccessHandler"      class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">      <b:property name="defaultTargetUrl" value="/index.do"></b:property>  </b:bean>  <b:bean id="simpleUrlAuthenticationFailureHandler"      class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">      <b:property name="defaultFailureUrl" value="/login.jsp?login_error=1"></b:property>  </b:bean>     最后是ValidateCodeAuthenticationFilter的源码:   Java代码   public class ValidateCodeAuthenticationFilter extends          UsernamePasswordAuthenticationFilter {        private boolean postOnly = true;      private boolean allowEmptyValidateCode = false;      private String sessionvalidateCodeField = DEFAULT_SESSION_VALIDATE_CODE_FIELD;      private String validateCodeParameter = DEFAULT_VALIDATE_CODE_PARAMETER;      public static final String DEFAULT_SESSION_VALIDATE_CODE_FIELD = "validateCode";      public static final String DEFAULT_VALIDATE_CODE_PARAMETER = "validateCode";      public static final String VALIDATE_CODE_FAILED_MSG_KEY = "validateCode.notEquals";        @Override      public Authentication attemptAuthentication(HttpServletRequest request,              HttpServletResponse response) throws AuthenticationException {          if (postOnly && !request.getMethod().equals("POST")) {              throw new AuthenticationServiceException(                      "Authentication method not supported: "                              + request.getMethod());          }            String username = StringUtils.trimToEmpty(obtainUsername(request));          String password = obtainPassword(request);          if (password == null) {              password = StringUtils.EMPTY;          }            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(                  username, password);            // Place the last username attempted into HttpSession for views          HttpSession session = request.getSession(false);            if (session != null || getAllowSessionCreation()) {              request.getSession().setAttribute(                      SPRING_SECURITY_LAST_USERNAME_KEY,                      TextEscapeUtils.escapeEntities(username));          }            // Allow subclasses to set the "details" property          setDetails(request, authRequest);          // check validate code          if (!isAllowEmptyValidateCode())              checkValidateCode(request);          return this.getAuthenticationManager().authenticate(authRequest);      }        /**      *       * <li>比较session中的验证码和用户输入的验证码是否相等</li>      *       */      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(VALIDATE_CODE_FAILED_MSG_KEY));          }      }        private String obtainValidateCodeParameter(HttpServletRequest request) {          return request.getParameter(validateCodeParameter);      }        protected String obtainSessionValidateCode(HttpServletRequest request) {          Object obj = request.getSession()                  .getAttribute(sessionvalidateCodeField);          return null == obj ? "" : obj.toString();      }        public boolean isPostOnly() {          return postOnly;      }        @Override      public void setPostOnly(boolean postOnly) {          this.postOnly = postOnly;      }        public String getValidateCodeName() {          return sessionvalidateCodeField;      }        public void setValidateCodeName(String validateCodeName) {          this.sessionvalidateCodeField = validateCodeName;      }        public boolean isAllowEmptyValidateCode() {          return allowEmptyValidateCode;      }        public void setAllowEmptyValidateCode(boolean allowEmptyValidateCode) {          this.allowEmptyValidateCode = allowEmptyValidateCode;      }    }     附件中有生成CODE图片的JSP(相对比较简单的,但基本可以满足应用),还有文章中用到的一些关键配置文件与源码。   生成验证码的jsp页面调用时直接<img src="./validateCode.jsp"  />即可,但刷新时,记得在URL上增加随机数的参数,不然会有缓存导致刷新失败。     添加验证码部分有参考: http://www.iteye.com/topic/720867

转载于:https://www.cnblogs.com/yanduanduan/p/5208893.html

相关资源:spring security3.1 实现验证码自定义登录
最新回复(0)