前言

  • • ExceptionTranslationFilter是SpringSecurity中负责处理异常的过滤器
  • • 这里的异常指的是下面两大异常
  • • AuthenticationException:认证异常
  • • AccessDeniedException:访问被拒绝异常
  • • 这里的异常是由SpringSecurity中负责进行权限校验的FilterSecurityInterceptor抛出的

1. ExceptionHandlingConfigurer

  • • ExceptionHandlingConfigurer是ExceptionTranslationFilter对应的配置类
  • • 也是默认开启的配置类之一image.png

1.1 常用方法

  • • 配置类中的常用方法都是为了配置下面的对象

public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
      extends AbstractHttpConfigurer<ExceptionHandlingConfigurer<H>, H> {

   private AuthenticationEntryPoint authenticationEntryPoint;

   private AccessDeniedHandler accessDeniedHandler;

   private LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<>();

   private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
}
  • • authenticationEntryPoint:身份验证入口点 是抛出认证异常才会执行的,比如说回到登录页的实现类LoginUrlAuthenticationEntryPoint

  • • accessDeniedHandler:捕获了访问被拒绝异常的处理器

  • • defaultEntryPointMappingsdefaultDeniedHandlerMappings:存放不同请求路径的的访问被拒绝的多个处理器 key:请求匹配器,比如说匹配/user value:对应的访问被拒绝的处理器

1.2 configure(...)

public void configure(H http) {
   //获得身份认证入口点
   AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
   //创建处理异常的过滤器,还传入了请求缓存器
   ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint,
         getRequestCache(http));
   //获得访问被拒绝处理器
   AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
   exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
   //进行objectPostProcessor处理
   exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
   //添加过滤器到httpSecurity中
   http.addFilter(exceptionTranslationFilter);
}
2. ExceptionTranslationFilter
  • • doFilter(...)方法的核心逻辑是try住下一个过滤器,而这个过滤器就是FilterSecurityInterceptor

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   try {
      chain.doFilter(request, response);
   }
   catch (IOException ex) {
      throw ex;
   }
   catch (Exception ex) {
      //尝试从堆栈跟踪中提取SpringSecurityException,不懂
      Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
      //拿到认证异常
      RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);
      //不是一个认证异常
      if (securityException == null) {
         securityException = (AccessDeniedException) this.throwableAnalyzer
               .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
      }
      //既不是认证异常也不是访问被拒绝异常,那就继续抛
      if (securityException == null) {
         rethrow(ex);
      }
      if (response.isCommitted()) {
         throw new ServletException("Unable to handle the Spring Security Exception "
               + "because the response is already committed.", ex);
      }
      //处理SpringSecurity异常
      handleSpringSecurityException(request, response, chain, securityException);
   }
}
  • • 而catch住的异常都会通过ThrowableAnalyzer转为认证异常和访问被拒绝异常,然后丢给handleSpringSecurityException(...)方法继续处理

  • • 再看handleSpringSecurityException(...)方法,这里就分别对于两个异常有不同的处理逻辑了

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain, RuntimeException exception) throws IOException, ServletException {
   //是一个认证异常
   if (exception instanceof AuthenticationException) {
      handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
   }
   //是一个访问被拒绝异常
   else if (exception instanceof AccessDeniedException) {
      handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
   }
}

2.1 handleAuthenticationException(...)

private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
          FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
       this.logger.trace("Sending to authentication entry point since authentication failed", exception);
       sendStartAuthentication(request, response, chain, exception);
    }

    /**
     * 处理认证异常
     */
    protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
          AuthenticationException reason) throws ServletException, IOException {
       //清除存储在线程级别的上下文策略的认证信息,HttpSession级别的会在SecurityContextPersistenceFilter的finally代码块中被更新
       //因为现有的认证不再被认为有效
       SecurityContext context = SecurityContextHolder.createEmptyContext();
       SecurityContextHolder.setContext(context);
       //将当前的请求放入请求缓存器
       //这样当重新登录后,还能将请求包装为这一次请求
       this.requestCache.saveRequest(request, response);
       //执行认证异常处理器
       this.authenticationEntryPoint.commence(request, response, reason);
    }

2.2 handleAccessDeniedException(...)

private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
   boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
   //当是匿名用户和记住我用户
   if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
               authentication), exception);
      }
      //还是当成一个认证异常处理
      //表示需要完整登录(用用户名和密码登录)
      sendStartAuthentication(request, response, chain,
            new InsufficientAuthenticationException(
                  this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
                        "Full authentication is required to access this resource")));
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace(
               LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
               exception);
      }
      //调用访问被拒绝处理器,进行处理
      this.accessDeniedHandler.handle(request, response, exception);
   }
}