SpringSecurity异常处理源码解析

SpringSecurity异常处理源码解析

异常处理主要是由ExceptionTranslationFilter完成的,而ExceptionTranslationFilter是由ExceptionHandlingConfigurer配置的,所以我们从ExceptionHandlingConfigurer开始解析

我们首先看ExceptionHandlingConfigurerconfigure(Httpsecurity security)方法:

@Override
public void configure(H http) {
    //获取AuthenticationEntryPoint
   AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);//1
   ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint,
         getRequestCache(http));//2
   AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);//3
   exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler//4
   http.addFilter(exceptionTranslationFilter);//5
}
  1. 这里AuthenticationEntryPoint是配置的主要类

    所以我们来解析getAuthenticationEntryPoint(http)方法:

    AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
       AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;//1
       if (entryPoint == null) {//2
          entryPoint = createDefaultEntryPoint(http);//3
       }
       return entryPoint;
    }
    
    1. 获取配置的authenticationEntryPoint
    2. 判断是否配置了authenticationEntryPoint
    3. 创建默认的EntryPoint

    我们继续看上面的createDefaultEntryPoint方法

    private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
       if (this.defaultEntryPointMappings.isEmpty()) {
          return new Http403ForbiddenEntryPoint();//1
       }
       if (this.defaultEntryPointMappings.size() == 1) {
          return this.defaultEntryPointMappings.values().iterator().next();//2
       }
       DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(
             this.defaultEntryPointMappings);
       entryPoint.setDefaultEntryPoint(this.defaultEntryPointMappings.values().iterator().next());
       return entryPoint;
    }
    
    1. 如果没有设置默认的EntryPoint则创建一个Http403ForbiddenEntryPoint,该Http403ForbiddenEntryPoint在认证失败的情况下返回403错误
    2. 如果有一个EntryPoint则返回该EntryPoint
    3. 如果有多个EntryPoint则返回DelegatingAuthenticationEntryPoint,默认使用第一个
  2. 创建一个ExceptionTranslationFilter用于处理所有异常,它将会加入到FilterChain

  3. 获取访问拒绝处理器,获取过程也和AuthenticationEntryPoint的获取过程一样

    ExceptionHandlingConfigurer.java

    AccessDeniedHandler getAccessDeniedHandler(H http) {
       AccessDeniedHandler deniedHandler = this.accessDeniedHandler;//1
       if (deniedHandler == null) {//2
          deniedHandler = createDefaultDeniedHandler(http);//3
       }
       return deniedHandler;
    }
    
    1. 获取配置的AccessDeniedHandler
    2. 判断是否配置了AccessDeniedHandler
    3. 创建默认的AccessDeniedHandler

    我们再看ExceptionHandlingConfigurercreateDefaultDeniedHandler这个方法

    ExceptionHandlingConfigurer.java

    private AccessDeniedHandler createDefaultDeniedHandler(H http) {
       if (this.defaultDeniedHandlerMappings.isEmpty()) {
          return new AccessDeniedHandlerImpl();
       }
       if (this.defaultDeniedHandlerMappings.size() == 1) {
          return this.defaultDeniedHandlerMappings.values().iterator().next();
       }
       return new RequestMatcherDelegatingAccessDeniedHandler(this.defaultDeniedHandlerMappings,
             new AccessDeniedHandlerImpl());
    }
    
    1. 如果没有设置默认的AccessDeniedHandler则创建一个AccessDeniedHandlerImpl,该AccessDeniedHandlerImpl在认证失败的情况下返回以下状态码

      HttpStatus.enum

      FORBIDDEN(403, Series.CLIENT_ERROR, "Forbidden"),
      
    2. 如果有一个AccessDeniedHandler则返回该AccessDeniedHandler

    3. 如果有多个AccessDeniedHandler则返回RequestMatcherDelegatingAccessDeniedHandler,默认使用第一个

  4. 添加访问拒绝处理器到HttpSecurity

分析完ExceptionTranslationFilter创建的源码,得知它主要是依靠AuthenticationEntryPointAccessDeniedHandler来处理异常,由此我们自定义异常处理的话就只需自定义EntryPointAccessDeniedHandler这两个类。

如何自定义这两个类呢?

ExceptionHandlingConfigurer自定义这两个类的方法:

ExceptionHandlingConfigurer.java

public ExceptionHandlingConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
   this.accessDeniedHandler = accessDeniedHandler;
   return this;
}//1
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
    this.authenticationEntryPoint = authenticationEntryPoint;
    return this;
}//2
  1. 设置accessDeniedHandler
  2. 设置authenticationEntryPoint

使用HttpSecurity中的两个方法进行获取ExceptionHandling并自定义

HttpSecurity.java

public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
    return getOrApply(new ExceptionHandlingConfigurer<>());
}//1
public HttpSecurity exceptionHandling(
      Customizer<ExceptionHandlingConfigurer<HttpSecurity>> exceptionHandlingCustomizer) throws Exception {
   exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));
   return HttpSecurity.this;
}//2
  1. 获取或创建一个ExceptionHandlingConfigurer
  2. 获取或创建一个使用自定义ExceptionHandingExceptionHandlingConfigurer

我们以第一种为例,自定义配置如下:

MySecurityConfig extends WebSecurityConfigurerAdapter

protected void configure(HttpSecurity httpSecurity) throws Exception {
    http.exceptionHandling()//1
        .accessDeniedHandler(new MyCustomAccessDeniedHandler())//2
        .authenticationEntryPoint(new MyCustomAuthenticationEntryPoint());//3
}
  1. 获取或创建一个ExceptionHandlingConfigurer
  2. ExceptionHandlingConfigurer使用自定义的AccessDeniedHandler()
  3. ExceptionHandlingConfigurer使用自定义的CustomAuthenticationEntryPoint()

解决了自定义ExceptionHandling之后我们在来看下面问题:

ExceptionTranlationFilter是如何处理异常的呢?

我们来看它的doFilter()方法

ExceptionTranlationFilter.java

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   try {//1
      chain.doFilter(request, response);
   }
   catch (IOException ex) {
      throw ex;
   }
   catch (Exception ex) {
      Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
      RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);//2
      if (securityException == null) {//3
         securityException = (AccessDeniedException) this.throwableAnalyzer
               .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
      }
      if (securityException == null) {//4
         rethrow(ex);
      }
      handleSpringSecurityException(request, response, chain, securityException);//5
   }
}
  1. 无异常则执行doFilter,有IOException则抛出IOException异常,其他的异常交给ExceptionTranslationFilter处理

  2. 获取AuthenticationException异常

  3. 如果没有AuthenticationException异常,则获取AccessDeniedException异常

  4. 如果都没有异常,则执行rethrow(ew)方法

    解析rethrow(ew)方法:

    ExceptionTranlationFilter.java

    private void rethrow(Exception ex) throws ServletException {
       if (ex instanceof ServletException) {
          throw (ServletException) ex;//1
       }
       if (ex instanceof RuntimeException) {
          throw (RuntimeException) ex;//2
       }
       throw new RuntimeException(ex);//3
    }
    
    1. 如果为ServletException异常,则抛出ServletException异常
    2. 如果为RuntimeException异常,则抛出RuntimeException异常
    3. 如果都不是则抛出RuntimeException异常
  5. 处理异常

    解析 handleSpringSecurityException(request, response, chain, securityException)源码

    ExceptionTranslationFilter.java

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

    对两个异常进行分别处理

    先解析handleAuthenticationException方法:

    ExceptionTranslationFilter.java

    private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
          FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
       sendStartAuthentication(request, response, chain, exception);
    }
    protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                           AuthenticationException reason) throws ServletException, IOException {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        SecurityContextHolder.setContext(context);
        this.requestCache.saveRequest(request, response);//1
        this.authenticationEntryPoint.commence(request, response, reason);//2
    }
    
    
    1. 保存Request信息(以便认证成功后调回原来的页面)

    2. 调用AuthenticationEntryPoint中的commenca方法

      由于AuthenticationEntryPoint实现类太多,这里只拿Http403ForbiddenEntryPoint进行解析:

      Http403ForbiddenEntryPoint

      public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2)
            throws IOException {
         logger.debug("Pre-authenticated entry point called. Rejecting access");
         response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
      }
      

      总之就是调用AuthenticationEntryPoint对Request和Response进行处理

    handleAccessDeniedException方法的处理流程和AuthenticationEntryPoint一样

    只是调用AccessDeniedException中的handle方法

    这里我们只介绍最默认的AccessDeniedHandlerImplhandle方法

    public void handle(HttpServletRequest request, HttpServletResponse response,
          AccessDeniedException accessDeniedException) throws IOException, ServletException {
       if (response.isCommitted()) {
          logger.trace("Did not write to response since already committed");
          return;
       }
       if (this.errorPage == null) {
          logger.debug("Responding with 403 status code");
          response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
          return;
       }
       // Put exception into request scope (perhaps of use to a view)
       request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
       // Set the 403 status code.
       response.setStatus(HttpStatus.FORBIDDEN.value());
       // forward to error page.
       if (logger.isDebugEnabled()) {
          logger.debug(LogMessage.format("Forwarding to %s with status code 403", this.errorPage));
       }
       request.getRequestDispatcher(this.errorPage).forward(request, response);
    }
    

    也是对Response进行处理,并让request跳转到错误页面

到这里异常处理源码解析就告一段落了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值