ModelAndView 未从 ExceptionHandler 显示

问题描述 投票:0回答:1

在 Spring Boot 3.3.3 中,我尝试使用默认的 HandlerExceptionResolver 来捕获安全过滤器链中发生的任何异常,以便在控制器使用的相同 exceptHandler ControllerAdvise 中处理它们。 我可以看到异常一直到 ExceptionHandler 的内部,但返回的 modelAndView 没有发生任何事情。 浏览器显示空白页面。

为了测试这一点,我有一个示例过滤器,其中遇到特定 URI 时抛出异常:

@Slf4j
public class SampleFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    log.debug( "entered SampleFilter" );
    if ( request.getRequestURI().endsWith("does-not-exist") ) {
      log.debug( "throwing exception" );
      throw new SampleException("does-not-exist does not exist!  Big surprise!");
    }
    filterChain.doFilter(request, response);
  }

}

另一个过滤器捕获所有异常并将它们发送到解析器

@Slf4j
@Component
public class FilterChainExceptionHandlerFilter  extends OncePerRequestFilter {

  @Autowired
  @Qualifier("handlerExceptionResolver")
  private HandlerExceptionResolver resolver;

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
      filterChain.doFilter(request, response);
    } catch (Exception e) {
      log.error("Spring Security Filter Chain Exception:", e);
      resolver.resolveException(request, response, null, e);
    }
  }
}

我将这些注册到 securityFilterChain 中

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

  @Autowired
  private FilterChainExceptionHandlerFilter filterChainExceptionHandlerFilter;

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .csrf(AbstractHttpConfigurer::disable)
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .anonymous(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
            .addFilterBefore( filterChainExceptionHandlerFilter, DisableEncodeUrlFilter.class )
            .addFilterBefore( new SampleFilter(), SecurityContextHolderFilter.class )
            .build();
  }

}

我在ControllerAdvice中定义了一个专门针对抛出异常的ExceptionHandler

@Slf4j
@ControllerAdvice
public class ExceptionController {

  @ExceptionHandler( {SampleException.class} )
  public ModelAndView handleSampleException(HttpServletRequest req, Exception e) {
    log.debug( "SampleException: setup error page for " + e.getLocalizedMessage() );
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error");
    modelAndView.addObject( "message", e.getLocalizedMessage() );
    return modelAndView;
  }

}

然后它应该被错误控制器拾取

@Slf4j
@Controller
public class SampleErrorController implements ErrorController {

  @RequestMapping( "/error" )
  public String showError( HttpServletRequest request,
                           Model model ) {
    Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    if (status != null) {
      Integer statusCode = Integer.valueOf(status.toString());
      model.addAttribute( "statusCode", statusCode );
    }

    return "error";
  }

}

并发送到我拥有的简单 error.html Thymeleaf 模板。

输入不存在且不会从 SampleFilter 引发异常的 URL 会很好地发送到模板。 我看到我的异常已记录在 ExceptionHandler 方法中,但 ModelAndView 似乎从未传递到 Thymeleaf。

日志

 :: Spring Boot ::                (v3.3.3)

2024-09-22 15:30:51,778 INFO  [restartedMain] o.s.b.StartupInfoLogger: Starting SampleApplication using Java 17.0.12 with PID 3084906 (/home/user/github/spring-servlet-filter-exception/target/classes started by user in /home/user/github/spring-servlet-filter-exception)
2024-09-22 15:30:51,779 DEBUG [restartedMain] o.s.b.StartupInfoLogger: Running with Spring Boot v3.3.3, Spring v6.1.12
2024-09-22 15:30:51,780 INFO  [restartedMain] o.s.b.SpringApplication: No active profile set, falling back to 1 default profile: "default"
2024-09-22 15:30:52,503 DEBUG [restartedMain] o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: ControllerAdvice beans: 1 @ExceptionHandler, 1 ResponseBodyAdvice
2024-09-22 15:30:52,543 DEBUG [restartedMain] o.s.w.f.GenericFilterBean: Filter 'filterChainExceptionHandlerFilter' configured for use
2024-09-22 15:30:52,575 WARN  [restartedMain] o.s.b.a.s.s.UserDetailsServiceAutoConfiguration: 

Using generated security password: d4c20939-34c6-4bc1-ab92-c176e0cba968

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2024-09-22 15:30:52,598 INFO  [restartedMain] o.s.s.c.a.a.c.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer: Global AuthenticationManager configured with UserDetailsService bean with name inMemoryUserDetailsManager
2024-09-22 15:30:52,673 DEBUG [restartedMain] o.s.w.s.h.AbstractHandlerMethodMapping: 2 mappings in 'requestMappingHandlerMapping'
2024-09-22 15:30:52,689 DEBUG [restartedMain] o.s.w.s.h.SimpleUrlHandlerMapping: Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2024-09-22 15:30:52,708 DEBUG [restartedMain] o.s.s.w.DefaultSecurityFilterChain: Will secure any request with filters: FilterChainExceptionHandlerFilter, DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SampleFilter, SecurityContextHolderFilter, HeaderWriterFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, ExceptionTranslationFilter
2024-09-22 15:30:52,727 WARN  [restartedMain] o.s.s.c.a.w.b.WebSecurity: 

********************************************************************
**********        Security debugging is enabled.       *************
**********    This may include sensitive information.  *************
**********      Do not use in a production system!     *************
********************************************************************


2024-09-22 15:30:52,764 DEBUG [restartedMain] o.s.w.s.m.m.a.RequestMappingHandlerAdapter: ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2024-09-22 15:30:52,852 INFO  [restartedMain] o.s.b.StartupInfoLogger: Started SampleApplication in 1.326 seconds (process running for 1.697)
2024-09-22 15:31:39,910 INFO  [http-nio-8080-exec-1] o.s.w.s.FrameworkServlet: Initializing Servlet 'dispatcherServlet'
2024-09-22 15:31:39,910 DEBUG [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet: Detected StandardServletMultipartResolver
2024-09-22 15:31:39,911 DEBUG [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet: Detected AcceptHeaderLocaleResolver
2024-09-22 15:31:39,911 DEBUG [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet: Detected FixedThemeResolver
2024-09-22 15:31:39,912 DEBUG [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet: Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@35c882e0
2024-09-22 15:31:39,912 DEBUG [http-nio-8080-exec-1] o.s.w.s.DispatcherServlet: Detected org.springframework.web.servlet.support.SessionFlashMapManager@5e3d86ed
2024-09-22 15:31:39,912 DEBUG [http-nio-8080-exec-1] o.s.w.s.FrameworkServlet: enableLoggingRequestDetails='true': request parameters and headers will be shown which may lead to unsafe logging of potentially sensitive data
2024-09-22 15:31:39,912 INFO  [http-nio-8080-exec-1] o.s.w.s.FrameworkServlet: Completed initialization in 2 ms
2024-09-22 15:31:39,940 DEBUG [http-nio-8080-exec-1] o.s.s.w.FilterChainProxy: Securing GET /does-not-exist
2024-09-22 15:31:39,943 DEBUG [http-nio-8080-exec-1] o.e.s.SampleFilter: entered SampleFilter
2024-09-22 15:31:39,944 DEBUG [http-nio-8080-exec-1] o.e.s.SampleFilter: throwing exception
2024-09-22 15:31:39,944 ERROR [http-nio-8080-exec-1] o.e.s.FilterChainExceptionHandlerFilter: Spring Security Filter Chain Exception:
org.example.controller.SampleException: does-not-exist does not exist!  Big surprise!
    at org.example.security.SampleFilter.doFilterInternal(SampleFilter.java:21)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.example.security.FilterChainExceptionHandlerFilter.doFilterInternal(FilterChainExceptionHandlerFilter.java:27)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
    at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
    at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:78)
    at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:67)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
    at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
    at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
    at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
    at java.base/java.lang.Thread.run(Thread.java:840)
2024-09-22 15:31:39,948 DEBUG [http-nio-8080-exec-1] o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: Using @ExceptionHandler org.example.controller.ExceptionController#handleSampleException(HttpServletRequest, Exception)
2024-09-22 15:31:39,950 DEBUG [http-nio-8080-exec-1] o.e.c.ExceptionController: SampleException: setup error page for does-not-exist does not exist!  Big surprise!
2024-09-22 15:31:39,958 WARN  [http-nio-8080-exec-1] o.s.w.s.h.AbstractHandlerExceptionResolver: Resolved [org.example.controller.SampleException: does-not-exist does not exist!  Big surprise!]

它停在那里并且没有呈现错误页面。

整个示例都可以运行

$ get clone https://github.com/smaring/spring-servlet-filter-exception.git
$ cd spring-servlet-filter-exception
$ mvn clean spring-boot:run

并打开浏览器访问http://localhost:8080/does-not-exist

spring spring-boot spring-mvc spring-security
1个回答
0
投票

感谢您的评论m-deinum。 它为我指明了正确的方向。

我尝试了SimpleMappingExceptionResolver和各种HandlerExceptionResolver策略,但没有成功。 当我意识到我唯一的希望是启动过滤器链的响应时,我想出了一个适合我的想法。 在我的过滤器中,我捕获抛出的异常,我传递了 SpringTemplateEngine,以便我可以从那里渲染我的 Thymeleaf 页面。

所以,我的异常处理过滤器就变成了

@Slf4j
public class FilterChainExceptionHandlerFilter extends OncePerRequestFilter {

  private final SpringTemplateEngine templateEngine;

  public FilterChainExceptionHandlerFilter( SpringTemplateEngine templateEngine ) {
    this.templateEngine = templateEngine;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
      filterChain.doFilter(request, response);
    } catch (Exception e) {
      log.error("caught exception ", e);

      if ( e instanceof SampleException ) {
        Context context = new Context();
        context.setVariable("statusCode", "123");
        context.setVariable("message", e.getLocalizedMessage() );
        String htmlTemplate = this.templateEngine.process("error", context);
        PrintWriter printWriter = response.getWriter();
        printWriter.println( htmlTemplate );
        printWriter.close();
      }

    }
  }
}

和我的安全过滤器链

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

  @Autowired
  private SpringTemplateEngine templateEngine;

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .csrf(AbstractHttpConfigurer::disable)
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .anonymous(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
            .addFilterBefore( new FilterChainExceptionHandlerFilter( templateEngine ), DisableEncodeUrlFilter.class )
            .addFilterBefore( new SampleFilter(), SecurityContextHolderFilter.class )
            .build();
  }

}

这正是我的目标。

我已经在 Github 上更新了示例项目,适合任何想要从控制器或过滤器对 Thymeleaf 页面进行异常处理的简单示例的人。

© www.soinside.com 2019 - 2024. All rights reserved.