I have a Spring Boot 3.4 service secured with Spring Security OAuth2.
My security configuration is standard:
@Bean
public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.exceptionHandling(eh -> eh.authenticationEntryPoint(handleException()));
http.securityMatcher("/api/**")
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/**").authenticated())
.oauth2ResourceServer(resourceServer ->
resourceServer.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
My endpoints bear the following annotation: @PreAuthorize(hasAuthority('SCOPE_MY_SCOPE'))
.
Furthermore, on my API models, I do validation using Jakarta Validation 3 (e.g., using annotations like @NotNull
on fields).
Given this configuration, I have the following scenario: I make an invalid request using a security bearer token that does not have the required scope.
The server responds with a 400, as the validation constraints fail. However, I expected to see a 403 being given this request; I think that Spring is exposing unnecessary information about my validation constraints to someone that is not authorized to call my endpoint. If I use a valid request, it indeed returns a 403, so the problem is with the order of the checks: I would like to see the security check, then the validation check, but seems it is going the other way around.
I have a Spring Boot 3.4 service secured with Spring Security OAuth2.
My security configuration is standard:
@Bean
public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.exceptionHandling(eh -> eh.authenticationEntryPoint(handleException()));
http.securityMatcher("/api/**")
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/**").authenticated())
.oauth2ResourceServer(resourceServer ->
resourceServer.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
My endpoints bear the following annotation: @PreAuthorize(hasAuthority('SCOPE_MY_SCOPE'))
.
Furthermore, on my API models, I do validation using Jakarta Validation 3 (e.g., using annotations like @NotNull
on fields).
Given this configuration, I have the following scenario: I make an invalid request using a security bearer token that does not have the required scope.
The server responds with a 400, as the validation constraints fail. However, I expected to see a 403 being given this request; I think that Spring is exposing unnecessary information about my validation constraints to someone that is not authorized to call my endpoint. If I use a valid request, it indeed returns a 403, so the problem is with the order of the checks: I would like to see the security check, then the validation check, but seems it is going the other way around.
Share Improve this question asked 11 hours ago Daniel PopDaniel Pop 5511 gold badge7 silver badges33 bronze badges 3 |2 Answers
Reset to default 1There isn't much you can do about this. First Spring Security does the request check (part of the SecurityFilterChain
). Next the control is handed over to the RequestMappingHandlerAdapter
which inspects the method, see the @Valid
annotation and first does the validation. If that goes well it calls the method at which point the MethodSecurityInterceptor
is being invoked to handle the security. As the MethodSecurityInterceptor
relies on the method being executed (it is a before aspect) it will not be invoked before that.
The only solution would be to do manual validation in your controller. You can do this by injecting the validator into the controller and call it yourself. Drawback is that you will loose error translation etc. as well.
Can you give any example for the invalid JWT? If you receive 400 code, your JWT Token might be malformed. For example;
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1617201283,
"scope": "read:data"
}
This token doesn't have "write:data" right. But if you send
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1617201283,
}
This token might be considered as malformed.
SecurityFilterChain
). Next the control is handed over to theRequestMappingHandlerAdapter
which inspects the method, see the@Valid
annotation and first does the validation. If that goes well it calls the method at which point theMethodSecurityInterceptor
is being invoked to handle the security. As theMethodSecurityInterceptor
relies on the method being executed (it is a before aspect) it will not be invoked before that. The only solution would be to do manual validation. – M. Deinum Commented 10 hours ago@Min
) with an invalid scope it still returns 401. I'm using Hibernate validator and@ControllerAdvice
to format errors nicer. I use@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
and@PreAuthorize("hasAnyAuthority('SCOPE_read', 'SCOPE_read-write')")
. – SledgeHammer Commented 7 hours ago