spring-security
spring-security copied to clipboard
Different behaviour of `@RolesAllowed` annotation on `@EnableMethodSecurity` vs `@EnableGlobalMethodSecurity`
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@RolesAllowed
value (code).
- this is because
-
After switching to the new annotation (
@EnableMethodSecurity(jsr250Enabled = true)
,/rolesAllowed_GUEST
works ok, but/rolesAllowed_ROLE_GUEST
returns 403 instead of 200:- this is because
ROLE_
prefix is added unconditionally (code), -
authorities
in AuthorityAuthorizationManager#isAuthorized method isROLE_ROLE_GUEST
in this case (would expectROLE_GUEST
for 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
@EnableMethodSecurity
annotation to@EnableGlobalMethodSecurity
and 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 |