1. 自定义user-service后,封装自定义异常信息返回
通常情况下,抛UsernameNotFoundException异常信息是捕捉不了,跟踪源码后发现
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
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="myUserDetailsService"> <!-- 密码加密方式 --> <password-encoder hash="md5" /> </authentication-provider> </authentication-manager>
现在修改如下:
<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();
public SpringSecurityMessageSource() { setBasename("org.springframework.security.messages"); }
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()); } }
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); } ... }
<custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />
<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>
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; } }