springspring-bootspring-securityspring-restspring-oauth2

Spring validating constraints before security scopes


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.


Solution

  • There 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.