You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Page with permitAll is no longer accessible via auto-configured MockMvc #28759
As asked on gitter I found an issue after upgrading to Spring Boot 2.6.0: running a @SpringBootTest with @AutoConfigureMockMvc a login page (not limited to) is no longer accessible after the upgrade. The same configuration that worked on Spring Boot 2.5.7 now triggers a 401. Tracing this lead me to the ErrorPageSecurityFilter.
Since 2.6.0 the initial request gets granted, but the (mock) filter chain goes through the ErrorPageSecurityFilter and denies in later.
For reasons I can't exactly remember I had multiple configuration extending from WebSecurityConfigurerAdapter which worked in Spring Boot 2.5.7. Merging the two configurations into one fixed the issue for me, but it does still sound like unforeseen and unwanted behavior.
The cause of the problem is that ErrorPageSecurityFilter is being called for a REQUEST dispatch despite only being registered for ERROR dispatches. This is a limitation of MockMvc that I had overlooked when working on the changes for #26356 with @mbhave. I've opened spring-projects/spring-framework#27717 so that the Framework team can consider an enhancement to MockMvc that would allow us to register filters for particular dispatcher types. In the meantime, we can update Boot's ErrorPageSecurityFilter to ignore non-ERROR requests.
changed the title [-]Page with permitAll no longer accessible via mockMvc after upgrade to 2.6.0[/-][+]Page with permitAll no longer accessible via MockMvc after upgrade to 2.6.0[/+]on Nov 22, 2021
changed the title [-]Page with permitAll no longer accessible via MockMvc after upgrade to 2.6.0[/-][+]Page with permitAll no longer accessible via auto-configured MockMvc after upgrade to 2.6.0[/+]on Nov 22, 2021
A workaround for this problem is to remove the error page security filter by adding the following bean to the configuration used in your application's tests:
I like to understand what's going on in Spring and especially security-wise, but here I feel a little lost:
1. Finding the root of the problem
I have turned on debugging on all my configs and set logging.level.org=TRACE and logging.level.com=TRACE and log all output streams while running a single test which used to pass and now fails. The only place where I see EITHER ErrorPageSecurityFilter or errorPageSecurityInterceptor (the filter registration bean) connected to this failed test is in the stacktrace:
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.6.0.jar:5.6.0]
at org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator.isAllowed(DefaultWebInvocationPrivilegeEvaluator.java:100) ~[spring-security-web-5.6.0.jar:5.6.0]
at org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator.isAllowed(DefaultWebInvocationPrivilegeEvaluator.java:67) ~[spring-security-web-5.6.0.jar:5.6.0]
at org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter.doFilter(ErrorPageSecurityFilter.java:58) ~[spring-boot-2.6.0.jar:2.6.0]
at javax.servlet.http.HttpFilter.doFilter(HttpFilter.java:57) ~[tomcat-embed-core-9.0.55.jar:4.0.FR]
....
Apart from that, the errorPageSecurityInterceptor is mentioned ONCE in quite a load of output :)
Was is from this that you found that this filter was the culprit or did I miss something?
2. What does ErrorPageSecurityFilter solve?
I've read the tickets about this problem but I still don't get it. So there used to be some discrepancy between how error messages were sent in connection to endpoints requiring authentication; if no credentials were given, you got a more juicy error message than when you supplied incorrect credentials. Seems a bit odd, I agree with that, but why did it take a new filter to solve it and exactly how does this filter solve it?
3. Role in the filter chain
As you understand, I did quite some debugging, but feel puzzled about the behaviour, even given this ErrorPageSecurityFilter. I've understood that this filter is installed outside of Spring Security and that it runs among the filters. I guess then that it is not Spring Security who denies access?
This is my error message with multiple configs where the lowest-order config denies all access (catch-all):
2021-11-23 10:24:42.450 TRACE 82423 --- [ Test worker] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (12/12)
2021-11-23 10:24:42.450 TRACE 82423 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_API_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_API_USER]] before authorizing
2021-11-23 10:24:42.450 TRACE 82423 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /api/bogus] with attributes [hasAnyRole('ROLE_API_USER')]
2021-11-23 10:24:42.450 DEBUG 82423 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /api/bogus] with attributes [hasAnyRole('ROLE_API_USER')]
2021-11-23 10:24:42.450 TRACE 82423 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Did not switch RunAs authentication since RunAsManager returned null
2021-11-23 10:24:42.450 DEBUG 82423 --- [ Test worker] o.s.security.web.FilterChainProxy : Secured GET /api/bogus
2021-11-23 10:24:42.450 TRACE 82423 --- [ Test worker] o.s.s.w.a.expression.WebExpressionVoter : Voted to deny authorization
2021-11-23 10:24:42.455 DEBUG 82423 --- [ Test worker] a.DefaultWebInvocationPrivilegeEvaluator : filter invocation [/api/bogus] denied for UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_API_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_API_USER]]
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.6.0.jar:5.6.0]
...
2021-11-23 10:24:42.456 TRACE 82423 --- [ Test worker] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
Without the last deny-all config, the above is turned into:
2021-11-23 10:28:24.524 TRACE 82954 --- [ Test worker] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (12/12)
2021-11-23 10:28:24.524 TRACE 82954 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_API_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_API_USER]] before authorizing
2021-11-23 10:28:24.524 TRACE 82954 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /api/bogus] with attributes [hasAnyRole('ROLE_API_USER')]
2021-11-23 10:28:24.524 DEBUG 82954 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /api/bogus] with attributes [hasAnyRole('ROLE_API_USER')]
2021-11-23 10:28:24.525 TRACE 82954 --- [ Test worker] o.s.s.w.a.i.FilterSecurityInterceptor : Did not switch RunAs authentication since RunAsManager returned null
2021-11-23 10:28:24.525 DEBUG 82954 --- [ Test worker] o.s.security.web.FilterChainProxy : Secured GET /api/bogus
2021-11-23 10:28:24.525 TRACE 82954 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : GET "/api/bogus", parameters={}, headers={masked} in DispatcherServlet ''
2021-11-23 10:28:24.526 TRACE 82954 --- [ Test worker] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'exampleEndpoint'
2021-11-23 10:28:24.526 TRACE 82954 --- [ Test worker] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.examplespringboot.ExampleEndpoint#bogus()
2021-11-23 10:28:24.526 TRACE 82954 --- [ Test worker] o.s.web.method.HandlerMethod : Arguments: []
2021-11-23 10:28:24.527 DEBUG 82954 --- [ Test worker] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/plain', given [*/*] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2021-11-23 10:28:24.527 TRACE 82954 --- [ Test worker] m.m.a.RequestResponseBodyMethodProcessor : Writing ["bogus"]
2021-11-23 10:28:24.528 TRACE 82954 --- [ Test worker] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
So it seems to me that in both cases Spring Security says YES, but in the first case, there is some problem when the ErrorPageSecurityFilter processes the request, but exactly what is the problem and why does it disappear once I remove the deny-all config? It seemsErrorPageSecurityFilter invokes DefaultWebInvocationPrivilegeEvaluator. This in turn seems to somehow call WebExpressionVoter (via some other class) which turns our request down... Why? And what is this class used for otherwise?
Note that I DO have an entry for /error path in my security config and it allows access to all.
I would LOVE to understand this better so if you have time, feel free to fill me in :)
changed the title [-]Page with permitAll no longer accessible via auto-configured MockMvc after upgrade to 2.6.0[/-][+]Page with permitAll is no longer accessible via auto-configured MockMvc[/+]on Nov 24, 2021
Activity
martinvisser commentedon Nov 20, 2021
Perhaps related to #26356 and/or #28741
wilkinsona commentedon Nov 22, 2021
Thanks for the sample, @martinvisser.
The cause of the problem is that
ErrorPageSecurityFilteris being called for aREQUESTdispatch despite only being registered forERRORdispatches. This is a limitation of MockMvc that I had overlooked when working on the changes for #26356 with @mbhave. I've opened spring-projects/spring-framework#27717 so that the Framework team can consider an enhancement to MockMvc that would allow us to register filters for particular dispatcher types. In the meantime, we can update Boot'sErrorPageSecurityFilterto ignore non-ERRORrequests.[-]Page with permitAll no longer accessible via mockMvc after upgrade to 2.6.0[/-][+]Page with permitAll no longer accessible via MockMvc after upgrade to 2.6.0[/+][-]Page with permitAll no longer accessible via MockMvc after upgrade to 2.6.0[/-][+]Page with permitAll no longer accessible via auto-configured MockMvc after upgrade to 2.6.0[/+]wilkinsona commentedon Nov 22, 2021
A workaround for this problem is to remove the error page security filter by adding the following bean to the configuration used in your application's tests:
steklopod commentedon Nov 22, 2021
I also have the same problem with
2.6.0only:Result
My test:
wilkinsona commentedon Nov 23, 2021
@steklopod That does look like the same problem. If you haven't done so already, please try the workaround.
steklopod commentedon Nov 23, 2021
Thank you. It solved my problem. I just put it my
@Configurationfile inbackend/src/test/kotlin/config/AnyTestConfig.kt:@Bean fun removeErrorSecurityFilter(): BeanFactoryPostProcessor = BeanFactoryPostProcessor { beanFactory: ConfigurableListableBeanFactory -> (beanFactory as DefaultListableBeanFactory).removeBeanDefinition("errorPageSecurityInterceptor") }fast-reflexes commentedon Nov 24, 2021
I opened a similar ticket in Spring Security's Github repo: spring-projects/spring-security#10544.
I also use multiple configurations and test repo is available here: https://github.com/fast-reflexes/spring-boot-bug
So...
I like to understand what's going on in Spring and especially security-wise, but here I feel a little lost:
1. Finding the root of the problem
I have turned on debugging on all my configs and set
logging.level.org=TRACEandlogging.level.com=TRACEand log all output streams while running a single test which used to pass and now fails. The only place where I see EITHERErrorPageSecurityFilterorerrorPageSecurityInterceptor(the filter registration bean) connected to this failed test is in the stacktrace:Apart from that, the
errorPageSecurityInterceptoris mentioned ONCE in quite a load of output :)Was is from this that you found that this filter was the culprit or did I miss something?
2. What does
ErrorPageSecurityFiltersolve?I've read the tickets about this problem but I still don't get it. So there used to be some discrepancy between how error messages were sent in connection to endpoints requiring authentication; if no credentials were given, you got a more juicy error message than when you supplied incorrect credentials. Seems a bit odd, I agree with that, but why did it take a new filter to solve it and exactly how does this filter solve it?
3. Role in the filter chain
As you understand, I did quite some debugging, but feel puzzled about the behaviour, even given this
ErrorPageSecurityFilter. I've understood that this filter is installed outside of Spring Security and that it runs among the filters. I guess then that it is not Spring Security who denies access?This is my error message with multiple configs where the lowest-order config denies all access (catch-all):
Without the last deny-all config, the above is turned into:
So it seems to me that in both cases Spring Security says YES, but in the first case, there is some problem when the
ErrorPageSecurityFilterprocesses the request, but exactly what is the problem and why does it disappear once I remove the deny-all config? It seemsErrorPageSecurityFilterinvokesDefaultWebInvocationPrivilegeEvaluator. This in turn seems to somehow callWebExpressionVoter(via some other class) which turns our request down... Why? And what is this class used for otherwise?Note that I DO have an entry for
/errorpath in my security config and it allows access to all.I would LOVE to understand this better so if you have time, feel free to fill me in :)
[-]Page with permitAll no longer accessible via auto-configured MockMvc after upgrade to 2.6.0[/-][+]Page with permitAll is no longer accessible via auto-configured MockMvc[/+]Adapt Sample
fast-reflexes commentedon Nov 26, 2021
I investigated the matter myself and found an additional bug. Explanations to the above and info about this bug can be found at #28818