Describe the bug
Consider the following controller:
@RestController
public class SecuredController {
@GetMapping(path = "/rolesAllowed_GUEST")
@RolesAllowed("GUEST")
public String rolesAllowed_GUEST() {
return "GUEST";
}
@GetMapping(path = "/rolesAllowed_ROLE_GUEST")
@RolesAllowed("ROLE_GUEST")
public String rolesAllowed_ROLE_GUEST() {
return "ROLE_GUEST";
}
}
-
When legacy
@EnableGlobalMethodSecurity(jsr250Enabled = true)is used, both endpoints are accessible when request is authenticated withROLE_GUEST:- this is because
ROLE_prefix is added only when not already present in@RolesAllowedvalue (code).
- this is because
-
After switching to the new annotation (
@EnableMethodSecurity(jsr250Enabled = true),/rolesAllowed_GUESTworks ok, but/rolesAllowed_ROLE_GUESTreturns 403 instead of 200:- this is because
ROLE_prefix is added unconditionally (code), authoritiesin AuthorityAuthorizationManager#isAuthorized method isROLE_ROLE_GUESTin this case (would expectROLE_GUESTfor both endpoints).
- this is because
To Reproduce
- Enable method security with
@EnableMethodSecurity(jsr250Enabled = true). - Create a REST Controller with
@RolesAllowed("ROLE_GUEST"). - Configure a simple
UserDetailsService:@Bean public UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(User.builder().username("guest").password("{noop}guest").roles("GUEST").build()); } - Start the server.
- Send a request with basic auth:
curl --request GET 'http://localhost:8080/rolesAllowed_ROLE_GUEST' --header 'Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=' - Observe the response code - it is 403 instead of 200.
- Stop the server, switch the
@EnableMethodSecurityannotation to@EnableGlobalMethodSecurityand retry the test. Observe the response code - it is 200.
Tested on Spring Security 5.7.2, but the code that unconditionally adds the prefix seems to be the same on main.
See the sample repository provided below for a complete example.
Expected behavior
@EnableMethodSecurity should behave in the same way as @EnableGlobalMethodSecurity regarding conditional adding of ROLE_ prefix.
Alternatively, the change should be documented in Method Security docs, as it can be breaking for some applications.
Sample
https://github.com/mgr32/spring-security-method-security-issue
Summary including other annotations (assuming HTTP request with proper Authorization header):
| Enabling annotation | Endpoint annotation | HTTP response status code |
| ----------- | ----------- | ---------|
| @EnableGlobalMethodSecurity | @RolesAllowed("GUEST") | :heavy_check_mark: 200 |
| @EnableMethodSecurity | @RolesAllowed("GUEST") | :heavy_check_mark: 200 |
| @EnableGlobalMethodSecurity | @RolesAllowed("ROLE_GUEST") | :heavy_check_mark: 200 |
| @EnableMethodSecurity | @RolesAllowed("ROLE_GUEST") | :x: :exclamation: 403 |
| @EnableGlobalMethodSecurity | @Secured("GUEST") | :x: 403 |
| @EnableMethodSecurity | @Secured("GUEST") | :x: 403 |
| @EnableGlobalMethodSecurity | @Secured("ROLE_GUEST") | :heavy_check_mark: 200 |
| @EnableMethodSecurity | @Secured("ROLE_GUEST") | :heavy_check_mark: 200 |
| @EnableGlobalMethodSecurity | @PreAuthorize("hasRole('GUEST')") | :heavy_check_mark: 200 |
| @EnableMethodSecurity | @PreAuthorize("hasRole('GUEST')") | :heavy_check_mark: 200 |
| @EnableGlobalMethodSecurity | @PreAuthorize("hasRole('ROLE_GUEST')") | :heavy_check_mark: 200 |
| @EnableMethodSecurity | @PreAuthorize("hasRole('ROLE_GUEST')") | :heavy_check_mark: 200 |
Comment From: jzheaux
Thanks, @mgr32, especially for the detailed tabular comparison. It seems more consistent to not conditionally add ROLE_. As such, I'm going to close this ticket, but also ensure that an example like this is included in the AuthorizationManager guide.