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;
- }
- }