This appears to have broken in 2.6.X as older versions (I tried 2.5.X) do not exhibit the behavior described below.
Step 1: provide a WebSecurityConfigurerAdapter and configure it with the STATELESS session creation policy:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/test-user").hasRole("USER")
.antMatchers("/test-admin").hasRole("ADMIN")
.antMatchers("/error").authenticated()
.and().httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER").and()
.withUser("admin").password("{noop}password").roles("ADMIN", "USER");
}
}
Step 2: setup a simple controller
@Controller
public class ExampleController {
@GetMapping(path = {"test-user", "test-admin"})
public void test(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_OK);
}
}
Step 3: run the application and send a request to a resource the user is not authorized to view
curl -v -u user:password http://localhost:8080/test-admin
Expected Behavior: the user receives a HTTP 403 Forbidden response status code and a response body (such as the below):
{
"timestamp":"2022-02-04T23:42:41.621+00:00",
"status":403,
"error":"Forbidden",
"path":"/test-admin"
}
Actual Behavior: the user receives a HTTP 403 Forbidden response status code with no response body
Note: if the STATELESS session creation policy is removed from the above configuration, it works as expected.
Comment From: snicoll
@datagitlies thanks for the report. By sample, we mean something we can run ourselves and not code in text we'd have to copy and paste with a chance of missing something that wasn't described. Can you move that in actual project please? You can do so by attaching a zip to this issue or sharing a link to a GitHub repository.
Comment From: datagitlies
Sure. The error-response-body.zip file contains a full sample you can run @snicoll
One more note. By changing the version of the spring-boot-starter-parent in the pom.xml file from 2.6.3 to 2.5.9 ... it works as expected again. Hence, I believe this is a problem introduced in 2.6.X
Comment From: mbhave
Thanks for the sample @datagitlies. This commit was added to support previously authorized users to access the error page. The fix covers all scenarios except the case where the error page security rules are different from the original request (which is the case here). We discussed that case and didn't think it would be a situation that would arise very often and thought it was better to restrict access when we don't have the Authentication (in case of stateless session policy) than to allow it.
Can you describe in more detail why you want an unauthorized user to view the error details? In this example, it seems like a regular user shouldn't be allowed to view the error details while trying to access an admin page.
We could make it easier to exclude the error security filter for users who want to revert to 2.5.x behavior. However, that would allow unauthenticated access to the error page. You can see that on the sample app with 2.5.9, a request with no credentials can view the error details. The same behavior can be achieved on 2.6.3 with a permitAll() for the error page.
Flagging for team meeting to see if there's anything we can do here.
Comment From: datagitlies
Can you describe in more detail why you want an unauthorized user to view the error details?
I think of it like this. If you have authenticated yourself with valid credentials and you are a user of my application... I owe it to you to clearly communicate error information to you.
Also, in my sample there is only one /error and it is for all users and all scenarios. Hence my sample uses .antMatchers("/error").authenticated() because "authenticated" includes both the "USER" and "ADMIN" roles. Even if I were to change it to .antMatchers("/error").hasAnyRole("USER", "ADMIN") this issue would remain. Are you thinking it would be better to have an error page for ADMIN users that is defined separately and secured differently than the error page for regular USER users @mbhave ? If yes, how likely is it that the error logic would be any different between the two? I think it's probably pretty common for applications to have a single, shared /error which is usually just the default one provided by spring boot.
Regardless, I believe the real issue here is the lost security context for STATELESS session. It appears that this new error security filter is treating a single request as if it were two separate requests which seems faulty if you can't consistently access the Authentication from the "first" request and apply it appropriately to the "second" request. Anything less than that the framework has to make assumptions or take an opinionated view of how it should work. Also, it sets up the scenario where the functionality is inconsistent. Stateful or stateless, I think the behavior should be consistent across both.
Comment From: svilen-ivanov-kubit
I'm also affected by this problem: the STATELESS requirement comes from the fact that I use JWT for authentication which makes this a common use case.
Comment From: wilkinsona
@svilen-ivanov-kubit, as @mbhave described above, you should be able to restore Boot 2.5's behaviour by using permitAll() when configuring access to /error. Can you please give that a try?
Comment From: datagitlies
@wilkinsona I understand what you and @mbhave provided with permitAll() is a workaround but has your thinking changed since https://github.com/spring-projects/spring-boot/issues/28953#issuecomment-990724906
If a user is authenticated, they should received a body in an error response without permitting all to access the error page.
Comment From: wilkinsona
My thinking hasn't changed, but what I said there wasn't in the context of a stateless session creation policy.
It's unfortunate that Spring Security does not preserve a user's authentication across multiple dispatches for the same original request when it has been configured not to use a session. I do not fully understand why the authentication cannot be preserved even without a session when only a single request has been made by the client. We'll discuss this with the Security team.
Given Spring Security's current behaviour, we can either allow access by anyone to the error page (as Boot 2.5 did) or we can secure it but require a stateful session policy (as Boot 2.6 now does). If you're using 2.6 with a stateless session policy, you can opt back into Boot 2.5's behaviour by using permitAll(). I don't really consider this to be a work around, more an accurate reflection of how Spring Security currently manages the authentication and how servlet container error pages work.
Comment From: svilen-ivanov-kubit
I tried the .permitAll() suggestion but without any luck. I see Jetty's default 401 HTML page instead of JSON response with 401 in 2.6.x:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 401 Unauthorized</title>
</head>
<body><h2>HTTP ERROR 401 Unauthorized</h2>
<table>
<tr><th>URI:</th><td>/....the path I request....</td></tr>
<tr><th>STATUS:</th><td>401</td></tr>
<tr><th>MESSAGE:</th><td>Unauthorized</td></tr>
<tr><th>SERVLET:</th><td>dispatcherServlet</td></tr>
</table>
</body>
</html>
I'll try to find out how my setup differs from the minimal reproducible project (except that I use JWT for authentication).
Comment From: mbhave
@svilen-ivanov-kubit A sample would be very helpful, thank you.
Comment From: mbhave
Blocked on https://github.com/spring-projects/spring-security/issues/10918.
Comment From: svilen-ivanov-kubit
@svilen-ivanov-kubit A sample would be very helpful, thank you.
I updated our WebSecurityConfigurerAdapter by removing some cruft and the issue is no longer reproducible. Thank you.
Comment From: philwebb
For 2.6 we're going to recommend that .permitAll() is used. For 2.7 we're going to auto-configure RequestAttributeSecurityContextRepository for the OAuth2 resource server and also document that users can configure it themselves.
Comment From: datagitlies
@philwebb yeah, I saw this
Introduced SecurityContextHolderFilter - Ability to require explicit saving of the SecurityContext
Assume that means spring-boot 2.6.x will not be using spring-security 5.7.x then?
Comment From: mbhave
@datagitlies Spring Boot 2.6.x uses Spring security 5.6.x, so yes it won't be using spring security 5.7.x
Comment From: mbhave
We think that auto-configuring RequestAttributeSecurityContextRepository for the OAuth2 auto-configuration is not required because this issue happens only when there are multiple users with different permissions and access to the originally logged in user is required. I am going to close this issue in favor of the documentation issue.