Spring Security教程(1)---- AuthenticationException异常详解

这个异常是在登录的时候出现错误时抛出的异常,比如账户锁定,证书失效等,先来看下AuthenticationException常用的的子类:

UsernameNotFoundException 用户找不到

BadCredentialsException 坏的凭据

AccountStatusException 用户状态异常它包含如下子类

AccountExpiredException 账户过期

LockedException账户锁定

DisabledException 账户不可用

CredentialsExpiredException 证书过期


常见的异常就这几个,还有很多不再一一赘述,仅仅为了展示,对后续的阅读没有什么影响

用户登录验证的过滤器是UsernamePasswordAuthenticationFilter,它继承自AbstractAuthenticationProcessingFilter。

今天研究的是看Spring如何处理AuthenticationException这个异常的,异常的处理一般都是doFilter中处理的,所以首先看AbstractAuthenticationProcessingFilter的doFilter中的代码

[java]  view plain copy
  1.    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.            throws IOException, ServletException {  
  3.   
  4. .......  
  5.   
  6.        try {  
  7.            authResult = attemptAuthentication(request, response);  
  8.            if (authResult == null) {  
  9.                // return immediately as subclass has indicated that it hasn't completed authentication  
  10.                return;  
  11.            }  
  12.            sessionStrategy.onAuthentication(authResult, request, response);  
  13.        } catch(InternalAuthenticationServiceException failed) {  
  14.            logger.error("An internal error occurred while trying to authenticate the user.", failed);  
  15.            unsuccessfulAuthentication(request, response, failed);  
  16.   
  17.            return;  
  18.        }catch (AuthenticationException failed) {  
  19.            // Authentication failed  
  20.            unsuccessfulAuthentication(request, response, failed);  
  21.   
  22.            return;  
  23.        }  
  24.   
  25.        ......  
  26.    }  
从这段代码中我们看到Spring将异常捕获后交给了unsuccessfulAuthentication这个方法来处理(InternalAuthenticationServiceException也是AuthenticationException异常的子类,这是一个内部认证服务异常)

[java]  view plain copy
  1. protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,  
  2.         AuthenticationException failed) throws IOException, ServletException {  
  3.     SecurityContextHolder.clearContext();  
  4.   
  5.     if (logger.isDebugEnabled()) {  
  6.         logger.debug("Authentication request failed: " + failed.toString());  
  7.         logger.debug("Updated SecurityContextHolder to contain null Authentication");  
  8.         logger.debug("Delegating to authentication failure handler " + failureHandler);  
  9.     }  
  10.   
  11.     rememberMeServices.loginFail(request, response);  
  12.   
  13.     failureHandler.onAuthenticationFailure(request, response, failed);  
  14. }  
unsuccessfulAuthentication又交给了failureHandler(AuthenticationFailureHandler)来处理,然后追踪failureHandler

[java]  view plain copy
  1. private AuthenticationFailureHandler failureHandler =   
  2.     new SimpleUrlAuthenticationFailureHandler();  
发现最终是由SimpleUrlAuthenticationFailureHandler这个类的onAuthenticationFailure方法来处理的,打开这个类,有两个核心方法
[java]  view plain copy
  1. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,  
  2.          AuthenticationException exception) throws IOException, ServletException {  
  3.   
  4.      if (defaultFailureUrl == null) {  
  5.          logger.debug("No failure URL set, sending 401 Unauthorized error");  
  6.   
  7.          response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());  
  8.      } else {  
  9.          saveException(request, exception);  
  10.   
  11.          if (forwardToDestination) {  
  12.              logger.debug("Forwarding to " + defaultFailureUrl);  
  13.   
  14.              request.getRequestDispatcher(defaultFailureUrl).forward(request, response);  
  15.          } else {  
  16.              logger.debug("Redirecting to " + defaultFailureUrl);  
  17.              redirectStrategy.sendRedirect(request, response, defaultFailureUrl);  
  18.          }  
  19.      }  
  20.  }  
  21.   
  22.  /** 
  23.   * Caches the {@code AuthenticationException} for use in view rendering. 
  24.   * <p> 
  25.   * If {@code forwardToDestination} is set to true, request scope will be used, otherwise it will attempt to store 
  26.   * the exception in the session. If there is no session and {@code allowSessionCreation} is {@code true} a session 
  27.   * will be created. Otherwise the exception will not be stored. 
  28.   */  
  29.  protected final void saveException(HttpServletRequest request, AuthenticationException exception) {  
  30.      if (forwardToDestination) {  
  31.          request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);  
  32.      } else {  
  33.          HttpSession session = request.getSession(false);  
  34.   
  35.          if (session != null || allowSessionCreation) {  
  36.              request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);  
  37.          }  
  38.      }  
  39.  }  
从onAuthenticationFailure中我们可以看出,如果没有设置defaultFailureUrl就发出401错误。(SC_UNAUTHORIZED是一个常量值是401),如果设置了defaultFailureUrl,调用saveException方法后跳转到defaultFailureUrl页面。

forwardToDestination是是否是服务器跳转,默认使用重定向即客户端跳转。

saveException字面意思是保存异常信息,其实就是把异常信息写入request或者Session中。

如果使用服务器跳转则写入request,客户端跳转则写入Session。

而key是WebAttributes.AUTHENTICATION_EXCEPTION打开WebAttributes找到这个常量

[java]  view plain copy
  1. public final class WebAttributes {  
  2.     
  3.     public static final String ACCESS_DENIED_403 = "SPRING_SECURITY_403_EXCEPTION";  
  4.   
  5.     public static final String AUTHENTICATION_EXCEPTION = "SPRING_SECURITY_LAST_EXCEPTION";  
  6.   
  7.     public static final String WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE =   
  8.     WebAttributes.class.getName() + ".WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE";  
  9. }  
发现其值为SPRING_SECURITY_LAST_EXCEPTION,这样的话我们就可以在页面上通过el表达式来获取到这个异常了

注意:saveException保存的是Session对象所以直接使用 ${SPRING_SECURITY_LAST_EXCEPTION}是获取不到异常信息的,需要使用${SPRING_SECURITY_LAST_EXCEPTION.message}

你可能感兴趣的:(Spring Security教程(1)---- AuthenticationException异常详解)