From spring boot 2.5.24 to last release 2.7.x, i face a strange behavior : i've got a postMethod in a controller annotated with @RolesAllowed, and the @RequestBody is annotated with @Valid (from JSR250) when i post a message with an unauthorized user and an invalid requestBody i receive a BAD_REQUEST error, but i would expect an UNAUTHORIZED error. Is it a normal behaviour ?
Here is how to reproduce : controller
@RestController
@RequestMapping("rest/api/security")
public class SecurityController {
@RolesAllowed("ADMIN")
@PostMapping(value = "/with-valid", produces = APPLICATION_JSON_VALUE)
public @ResponseBody Data withValid(@RequestBody @Valid Data data) {
return data;
}
}
dto
public class Data {
@NotBlank
private String str;
}
Spring context
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().permitAll()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic();
}
@Bean
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
User.UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("U809583")
.password("password")
.roles("ADMIN")
.build();
UserDetails admin = users
.username("U809584")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
test
@SpringBootTest(classes = {DemoApplication.class},
webEnvironment = RANDOM_PORT)
public class SecurityE2EIT {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void withSecurityAndUnauthorizedUserAndValidation() {
Data data = new Data();
HttpEntity<Data> httpEntity = new HttpEntity<>(data, createHeaders());
String url = "http://localhost:" + port + "/rest/api/security/with-valid";
ResponseEntity<String> response = this.restTemplate
.withBasicAuth("U809584", "password")
.exchange(url, HttpMethod.POST, httpEntity, String.class);
assertThat(response.getStatusCode()).isEqualTo(FORBIDDEN);
}
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add(ACCEPT, "application/json;application/problem+json");
return headers;
}
}
Michael
Comment From: marcusdacoregio
Hi @zouroto, thanks for the report.
I tried to simulate using the code that you provided but the test passes with 403 Unauthorized. Is there something that I'm missing?
Maybe if you can provide a minimal, reproducible example that I can clone and run it should be better.
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: zouroto
Hello sry for the late answer
Please, find an exemple on the github link demo repo
Comment From: marcusdacoregio
Thanks for the example @zouroto.
The validation is done in the filter chain before the request reaches the controller, when the payload from the request is binded to your Data class. The @RollesAllowed("ADMIN") validation will trigger before the method from the controller is invoked, this means that the authorization check will be performed after the filter chain completes and before the method is actually invoked. That said, it's expected that the payload validation will be performed before the authorization check from @RolesAllowed.
You could move your authorization check to the security DSL in order to make the authorization check during the filter chain:
http
.authorizeRequests()
.antMatchers("/rest/api/security/with-valid").hasRole("ADMIN")
.anyRequest().authenticated()
I'm closing this as invalid but feel free to continue the discussion if you need further help.