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
ResponseStatusException no longer returning response body in 2.6.1 using spring security #28953
we found that after upgrading to spring boot 2.6.1 that the response body from the ResponseStatusException is no longer being populated even for authenticated users
It looks like the spring boot forwards onto the /error page but the BearerTokenAuthenticationFIlter which extends the OncePerRequestFilter doesn't add the necessary authentication to the spring security context when in error state.
This means that we then hit #26356 and the body is empty.
The behaviour you have observed is intended with Spring Boot 2.6. Your security configuration requires authentication to access everything but /favicon.ico, /csrf, /health/**, /info, /webjars/**, and /v2/api-docs. This means that you have secured Spring Boot's error page at /error and, thanks to the changes in #26356, an unauthenticated user no longer receives a full error response.
If you want any user, authenticated or not, to receive Spring Boot's default error response, you should permit access to /error. Applying this diff to your application fixes the failing test:
I think the important behavioural change here in 2.6 is that authenticated users no longer receive a full error response if the application relies on the default error handler. It would be good to document this change and provide the recommended solution.
It does sound therefore that the default error handler should not be used. Instead I guess the application could catch the exception and return a ResponseEntity directly or alternatively throw a custom exception and then implement a @ExceptionHandler that returns a ResponseEntity.
I think the important behavioural change here in 2.6 is that authenticated users no longer receive a full error response if the application relies on the default error handler.
That shouldn't be the case.
the recommended solution to that problem is to allow the error page top be accessible to everyone
That is not the recommendation. If a user is authenticated, they should received a body in an error response without permitting all to access the error page. If that's not happening, then we need to investigate further. It could be that there's a faulty filter involved (as I believe was the case in the second issue to which you linked above), you're hitting a limitation of Spring Security, or something else.
The user in the sample project above is fully authenticated but they are not receiving a body in the error response.
It looks like the spring boot forwards onto the /error page but the BearerTokenAuthenticationFIlter which extends the OncePerRequestFilter doesn't add the necessary authentication to the spring security context when in error state.
Should I therefore raise a bug against the BearerTokenAuthenticationFIlter? (I presume other filters will be affected as well)
Apologies, @SimonMitchellMOJ, I had misunderstood the problem that the sample was demonstrating.
The problem is due to Spring Security's SecurityContextPersistenceFilter behaving differently depending on the authentication method that is being used. When using basic authentication, the authentication from the initial request is reinstated when the request fails and is forwarded to Boot's error page:
By contrast, when using a bearer token, the forward to /error is done with an anonymous authentication rather than the AuthAwareAuthenticationToken that was initially established:
The newly introduced ErrorPageSecurityFilter is built on the assumption that Spring Security will behave consistently in terms of the authentication that's available when a request is forwarded. The above shows that the assumption doesn't hold true when using a bearer token. We'll need to discuss this with the Security team to figure out the best course of action.
Reacted by petergphillips, Luca Graf and Ionescu Tiberiu
I'm seeing the same problem in our applications. We wrote our own custom Authentication. We're also not seeing response bodys on our requests anymore. For 2.5.7 it was working fine.
It boils down to the ErrorController.error(HttpServletRequest request) not getting called in 2.6.1 from what I found out.
I tried the current snapshot of 2.6.2 with the same outcome, no response body. I tried adding /error to the list of permitted paths but this also didn't bring back the response body.
Its relay unfortunate because this is an update blocker for us. Especially regarding the new log4j cve it would be really nice to be able to upgrade as soon as 2.6.2 is out (of course we temporally fixed it by forcing log4j to be version 2.15.0 in our gradle file)
@cmdjulian Can you provide a minimal sample that we can use to replicate the issue, please? You're most likely running into the same issue but I want to be sure since you mentioned custom Authentication and the original issue was caused due to the Authentication not being available to the error dispatch due to a session policy of STATELESS.
We use SessionCreationPolicy.STATELESS as well. We created a custom Authentication implementation to enrich the SecurityContext with more data. Under the hood it just extracts a JWT present in the Authentication header of a given request.
The code can be pictures as following:
publicclassJwtAuthorizationFilterextendsOncePerRequestFilter {
publicstaticfinalStringBEARER_TOKEN_PREFIX = "Bearer ";
/** * Put token into Spring Security Context, so that {@link JwtAuthenticationProvider} * can verify the tokens validity. */@OverrideprotectedvoiddoFilterInternal(@NonNullHttpServletRequestreq, @NonNullHttpServletResponseres, FilterChainchain)
throwsIOException, ServletException {
getJwtFromRequest(req).ifPresent(jwtCredentials -> {
JwtAuthenticationTokenjwtAuthenticationToken = JwtAuthenticationToken.builder()
.token(jwtCredentials)
.details(newWebAuthenticationDetailsSource().buildDetails(req))
.authenticated(false)
.build();
varcontext = SecurityContextHolder.createEmptyContext();
context.setAuthentication(jwtAuthenticationToken);
SecurityContextHolder.setContext(context);
});
chain.doFilter(req, res);
}
/** * Extract JWT value from Request header. */privateOptional<String> getJwtFromRequest(HttpServletRequestrequest) {
Stringtoken = request.getHeader(HttpHeaders.AUTHORIZATION);
returnStringUtils.startsWith(token, BEARER_TOKEN_PREFIX)
? Optional.of(token.substring(BEARER_TOKEN_PREFIX.length()))
: Optional.empty();
}
}
The JwtAuthenticationToken just implements Authentication without any extra buzz. If you still want me to submit a complete example let me know.
Except for not using Spring OAuth2 its the same as in the example provided example
@mbhave I just updated to 2.6.2 and the behavior is the same... I'm not sure d9d161c actually fixed the issue completely. The problem (for me) seems to be ErrorPageSecurityFilter.java#L80 - snippet below:
The authentication even for an authenticated user is always an instance of AnonymousAuthenticationToken - In my scenario the user is trying to access a resource they are not allowed to see. Which, I'm expecting an HTTP 403 response but I would also expect to see the error body (because the user is authenticated). Even setting .antMatchers("/error").permitAll() does not fix the issue. Why is the authentication from the SecurityContextHolder being wiped out?
@mbhave I just updated to 2.6.2 and the behavior is the same... I'm not sure d9d161c actually fixed the issue completely. The problem (for me) seems to be ErrorPageSecurityFilter.java#L80 - snippet below:
The authentication even for an authenticated user is always an instance of AnonymousAuthenticationToken - In my scenario the user is trying to access a resource they are not allowed to see. Which, I'm expecting a HTTP 403 response but I would also expect to see the error body (because the user is authenticated). Even setting .antMatchers("/error").permitAll() does not fix the issue. Why is the authentication from the SecurityContextHolder being wiped out?
@mbhave@datagitlies We have the same scenario: Show a (custom) spring boot error page if a user has no access (403) to a resource. As far i can tell the current ErrorPageSecurityFilter implementation does not support this case.
The implementation call WebInvocationPrivilegeEvaluator.isAllowed(uri, authentication) in case of an HTTP 401/403.
During debugging i observed the following:
The ErrorPageSecurityFilter use HttpServletRequest.getRequestURI() as uri parameter for the WebInvocationPrivilegeEvaluator.isAllowed(uri, authentication) method, which contains the context path. The JavaDoc notes that the context-path should be excluded in the uri parameter
This seems to be the reason that a matcher like .antMatchers("/error").permitAll() will not match in this case. Instead the matcher must additionally include the context-path to match.
Like already described by @datagitlies without access to the actual authentication (in case of @Transient authentication or STATELESS session policy) matchers like .antMatchers("/error").authenticated() will not work.
Are these known limitations, or is the described case not meant to be implemented by using spring error pages?
@lgraf I believe that the .antMatcher(...) always needs the context path in the pattern. You might want to try mvcMatcher(...) instead. If you're still having problems that you think are caused by a bug can you please open a new issue and attach a sample project that we can run and debug.
Activity
wilkinsona commentedon Dec 9, 2021
Thanks for the sample.
The behaviour you have observed is intended with Spring Boot 2.6. Your security configuration requires authentication to access everything but
/favicon.ico,/csrf,/health/**,/info,/webjars/**, and/v2/api-docs. This means that you have secured Spring Boot's error page at/errorand, thanks to the changes in #26356, an unauthenticated user no longer receives a full error response.If you want any user, authenticated or not, to receive Spring Boot's default error response, you should permit access to
/error. Applying this diff to your application fixes the failing test:petergphillips commentedon Dec 9, 2021
I think the important behavioural change here in 2.6 is that authenticated users no longer receive a full error response if the application relies on the default error handler. It would be good to document this change and provide the recommended solution.
It does feel a bit strange though that due to Error page is accessible when no credentials are provided we have now ended up with Error page is not accessible when credentials are provided and the recommended solution to that problem is to allow the error page to be accessible to everyone!
It does sound therefore that the default error handler should not be used. Instead I guess the application could catch the exception and return a
ResponseEntitydirectly or alternatively throw a custom exception and then implement a@ExceptionHandlerthat returns aResponseEntity.wilkinsona commentedon Dec 10, 2021
That shouldn't be the case.
That is not the recommendation. If a user is authenticated, they should received a body in an error response without permitting all to access the error page. If that's not happening, then we need to investigate further. It could be that there's a faulty filter involved (as I believe was the case in the second issue to which you linked above), you're hitting a limitation of Spring Security, or something else.
simon-mitchell commentedon Dec 10, 2021
The user in the sample project above is fully authenticated but they are not receiving a body in the error response.
It looks like the spring boot forwards onto the /error page but the BearerTokenAuthenticationFIlter which extends the OncePerRequestFilter doesn't add the necessary authentication to the spring security context when in error state.
Should I therefore raise a bug against the BearerTokenAuthenticationFIlter? (I presume other filters will be affected as well)
wilkinsona commentedon Dec 10, 2021
Apologies, @SimonMitchellMOJ, I had misunderstood the problem that the sample was demonstrating.
The problem is due to Spring Security's
SecurityContextPersistenceFilterbehaving differently depending on the authentication method that is being used. When using basic authentication, the authentication from the initial request is reinstated when the request fails and is forwarded to Boot's error page:By contrast, when using a bearer token, the forward to
/erroris done with an anonymous authentication rather than theAuthAwareAuthenticationTokenthat was initially established:The newly introduced
ErrorPageSecurityFilteris built on the assumption that Spring Security will behave consistently in terms of the authentication that's available when a request is forwarded. The above shows that the assumption doesn't hold true when using a bearer token. We'll need to discuss this with the Security team to figure out the best course of action.cmdjulian commentedon Dec 13, 2021
I'm seeing the same problem in our applications. We wrote our own custom
Authentication. We're also not seeing response bodys on our requests anymore. For2.5.7it was working fine.It boils down to the
ErrorController.error(HttpServletRequest request)not getting called in2.6.1from what I found out.I tried the current snapshot of
2.6.2with the same outcome, no response body. I tried adding/errorto the list of permitted paths but this also didn't bring back the response body.Its relay unfortunate because this is an update blocker for us. Especially regarding the new log4j cve it would be really nice to be able to upgrade as soon as 2.6.2 is out (of course we temporally fixed it by forcing log4j to be version 2.15.0 in our gradle file)
1 remaining item
mbhave commentedon Dec 13, 2021
@cmdjulian Can you provide a minimal sample that we can use to replicate the issue, please? You're most likely running into the same issue but I want to be sure since you mentioned custom
Authenticationand the original issue was caused due to theAuthenticationnot being available to the error dispatch due to a session policy ofSTATELESS.cmdjulian commentedon Dec 14, 2021
We use
SessionCreationPolicy.STATELESSas well. We created a customAuthenticationimplementation to enrich theSecurityContextwith more data. Under the hood it just extracts a JWT present in theAuthenticationheader of a given request.The code can be pictures as following:
The
JwtAuthenticationTokenjust implementsAuthenticationwithout any extra buzz. If you still want me to submit a complete example let me know.Except for not using Spring OAuth2 its the same as in the example provided example
mjustin commentedon Dec 17, 2021
Is there a possible (temporary) workaround within an app being upgraded from 2.5.x to 2.6.1, or are we pretty much stuck waiting for 2.6.2?
mbhave commentedon Dec 17, 2021
@mjustin A workaround would be to remove the
ErrorPageSecurityFilteras described in this comment.mjustin commentedon Dec 17, 2021
Thanks. Won't that have the side effect of also enabling error pages when the user is not logged authenticated, though?
Allow previously authorized users to access the error page
datagitlies commentedon Jan 3, 2022
@mbhave I just updated to
2.6.2and the behavior is the same... I'm not sure d9d161c actually fixed the issue completely. The problem (for me) seems to be ErrorPageSecurityFilter.java#L80 - snippet below:The
authenticationeven for an authenticated user is always an instance ofAnonymousAuthenticationToken- In my scenario the user is trying to access a resource they are not allowed to see. Which, I'm expecting an HTTP 403 response but I would also expect to see the error body (because the user is authenticated). Even setting.antMatchers("/error").permitAll()does not fix the issue. Why is theauthenticationfrom theSecurityContextHolderbeing wiped out?mjustin commentedon Jan 3, 2022
I will say this fixed the specific issue I was having, so it looks to be at least a partial fix.
lgraf commentedon Jan 4, 2022
@mbhave @datagitlies We have the same scenario: Show a (custom) spring boot error page if a user has no access (403) to a resource. As far i can tell the current
ErrorPageSecurityFilterimplementation does not support this case.The implementation call
WebInvocationPrivilegeEvaluator.isAllowed(uri, authentication)in case of an HTTP401/403.During debugging i observed the following:
ErrorPageSecurityFilteruseHttpServletRequest.getRequestURI()asuriparameter for theWebInvocationPrivilegeEvaluator.isAllowed(uri, authentication)method, which contains the context path. The JavaDoc notes that the context-path should be excluded in theuriparameterThis seems to be the reason that a matcher like
.antMatchers("/error").permitAll()will not match in this case. Instead the matcher must additionally include the context-path to match.authentication(in case of@Transientauthentication orSTATELESSsession policy) matchers like.antMatchers("/error").authenticated()will not work.Are these known limitations, or is the described case not meant to be implemented by using spring error pages?
philwebb commentedon Jan 4, 2022
@lgraf I believe that the
.antMatcher(...)always needs the context path in the pattern. You might want to trymvcMatcher(...)instead. If you're still having problems that you think are caused by a bug can you please open a new issue and attach a sample project that we can run and debug.