spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

Add integration test to ensure ErrorPageSecurityFilter works with custom endpoint matchers

Open jeffbswope opened this issue 3 years ago • 4 comments

Starting with 3.0.0-M1, a second WebSecurityConfigurer appears to be triggering "ServletContext must not be null" underneath the ErrorPageSecurityFilter when a request fails authentication.

We have a library which configures security for the actuator, then applications will configure their own security. This works fine so far up through 2.6.3, although I'm happy to hear if we are doing something unexpected.

Testing with 3.0.0-M1 all tests in this configuration which hit 401/403 errors are throwing this error from the actuator-specific matcher in RequestMatcherDelegatingWebInvocationPrivilegeEvaluator#delegates.

I know there's a lot going on with ErrorPageSecurityFilter recently but I did not see this problem in particular called out anywhere.

I was able to isolate this into minimal repro here, where the error can be found/eliminated based on enabling/disabling the second WebSecurityConfigurer: https://github.com/jeffbswope/null-servletcontext-errorpagefilter

java.lang.IllegalArgumentException: ServletContext must not be null
	at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:112) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext(WebApplicationContextUtils.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.boot.security.servlet.ApplicationContextRequestMatcher.matches(ApplicationContextRequestMatcher.java:58) ~[spring-boot-3.0.0-M1.jar:3.0.0-M1]
	at org.springframework.security.web.DefaultSecurityFilterChain.matches(DefaultSecurityFilterChain.java:67) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.getDelegate(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java:115) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.isAllowed(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java:66) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.isAllowed(ErrorPageSecurityFilter.java:83) ~[spring-boot-3.0.0-M1.jar:3.0.0-M1]
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.doFilter(ErrorPageSecurityFilter.java:71) ~[spring-boot-3.0.0-M1.jar:3.0.0-M1]
	at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.doFilter(ErrorPageSecurityFilter.java:65) ~[spring-boot-3.0.0-M1.jar:3.0.0-M1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:87) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-6.0.0-M1.jar:6.0.0-M1]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.0-M2.jar:6.0.0-M2]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:691) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:443) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:367) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:295) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:387) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:233) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:355) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:866) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1708) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.0.16.jar:10.0.16]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

jeffbswope avatar Jan 25 '22 19:01 jeffbswope

One thing I hadn't bothered to notice before is that in that reproduction when running the application you do get the correct 401/403, but it comes with HTML (apparently from Tomcat) and the java.lang.IllegalArgumentException: ServletContext must not be null exception is spit out in the log anyway.

jeffbswope avatar Jan 25 '22 19:01 jeffbswope

It looks like this is an issue that's been triggered by the fix for https://github.com/spring-projects/spring-security/issues/10554. If I update the sample with snapshot repos, downgrade to Spring Boot 2.6.3 and add ext['spring-security.version'] = '5.6.2-SNAPSHOT' then I get the same exception.

It looks like RequestMatcherDelegatingWebInvocationPrivilegeEvaluator is creating a FilterInvocation that uses a DummyRequest which ultimately means our ApplicationContextRequestMatcher fails.

This feels similar to https://github.com/spring-projects/spring-security/issues/10208. I'll open a Spring Security issue.

philwebb avatar Jan 25 '22 20:01 philwebb

I've opened https://github.com/spring-projects/spring-security/issues/10779 which you may want to subscribe to. Thanks for raising the issue.

philwebb avatar Jan 25 '22 20:01 philwebb

Reopening because it struck me that we didn't have any test in 3.0.x fail so it would be good to add one.

philwebb avatar Jan 26 '22 18:01 philwebb

We since removed ErrorPageSecurityFilter so I don't this is needed any more.

wilkinsona avatar Apr 22 '24 08:04 wilkinsona