In spring boot 3.3.3 I’m trying to use the default HandlerExceptionResolver to capture any exceptions that happen in the security filter chain to process them inside the same ExceptHandler ControllerAdvise that controllers use. I can see the exception making it all the way to the inside of the ExceptionHandler but nothing every happens with the returned modelAndView. The browser shows a blank page.
To test this, I have a sample filter wherein I throw an exception when a specific URI is encountered:
@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);
}
}
another filter catches all exceptions and sends them to the resolver
@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);
}
}
}
I register these in the 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();
}
}
I define an ExceptionHandler specific for the thrown exception in ControllerAdvice
@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;
}
}
It SHOULD then get picked up by the error controller
@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";
}
}
and sent on to the simple error.html Thymeleaf template I have.
Entering a URL that doesn’t exist and doesn’t throw an exception from the SampleFilter gets sent to the template just fine. I see my exception get logged in the ExceptionHandler methods, but the ModelAndView never seems to get passed on to Thymeleaf.
Log
:: 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!]
It stops there and no error page is rendered.
This entire sample can be run
$ get clone https://github.com/smaring/spring-servlet-filter-exception.git
$ cd spring-servlet-filter-exception
$ mvn clean spring-boot:run
and opening a browser to http://localhost:8080/does-not-exist
1