spring-bootfilterspring-security

HandlerExceptionResolver in exception handler doesn't work


thank you for your time. I use Spring boot 3.4.0 and I want to throw an exception in filter, but it doesn't work...

Everything is correct according to the breakpoints and logs, but my response body is empty.

The filter:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, 
            HttpServletResponse response, 
            FilterChain filterChain) throws ServletException, IOException {
        
        String token = request.getHeader("Authorization");
        
        try {
            
            if (token.startsWith("Basic ")) {

                String codeId = request.getParameter("codeId");
                String text = request.getParameter("text");

                CaptchaCode captchaCode = new CaptchaCode(codeId, text, null);

                captchaCodeApplicationService.isCoptchaCodeValid(captchaCode);

            }
            ...
        
        } catch (ValidationException e) {
            resolver.resolveException(request, response, null, e);
        }
        
    }

}

The advice:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<?> handlerValidationException(ValidationException e, WebRequest request) {
        
        ExceptionDetails details = new ExceptionDetails(
                LocalDateTime.now(),
                HttpStatus.BAD_REQUEST.name(),
                request.getDescription(false),
                e.getMessage()
                );
        
        return new ResponseEntity<>(details, HttpStatus.BAD_REQUEST);
    }
    
}

The securityConfig:

@Configuration
@EnableWebSecurity
public class SecuirtyConfig {
    
    @Autowired
    UserApplicationService userApplicationService;
    
    @Autowired
    private JwtAuthenticationFilter jwtFilter;
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        
        return httpSecurity.authorizeHttpRequests(registry -> {
            registry.requestMatchers("/api/v1/auth/**").permitAll();
            registry.requestMatchers("/api/v1/users/**").permitAll();
            registry.anyRequest().authenticated();
        }).formLogin(formLogin -> formLogin.permitAll())
                .csrf(httpSecurityCsrfConfig -> httpSecurityCsrfConfig.disable())
                .cors(httpSecurityCorsConfig -> httpSecurityCorsConfig.disable())
                .sessionManagement(httpSecuritySessionConfig -> httpSecuritySessionConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .httpBasic(Customizer.withDefaults())
                .build();
        
    }
    
    @Bean
    public AuthenticationProvider authenticationProvider() throws Exception {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userApplicationService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        
        return authenticationProvider;
    }
    
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(authenticationProvider());
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() throws Exception {
        return new BCryptPasswordEncoder();
    }

}

And the method that throws exception:

public boolean isCoptchaCodeValid(CaptchaCode captchaCode) throws ValidationException {
    ...
}

And the response: response next photo from the response


Solution

  • I quick test with the below code, gives me this body:

    {"now":"2024-12-20T14:53:02.339526733","name":"BAD_REQUEST","description":"uri=/backend-spring-boot/graphiql","message":"Invalid token"}
    

    ExceptionDetails

    import java.time.LocalDateTime;
    
    public record ExceptionDetails(
            LocalDateTime now,
            String name,
            String description,
            String message) {
    }
    

    JwtAuthenticationFilter

    import jakarta.servlet.FilterChain;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.lang.NonNull;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    
    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
        @Autowired
        @Qualifier("handlerExceptionResolver")
        private HandlerExceptionResolver resolver;
    
        @Override
        protected void doFilterInternal(
                @NonNull HttpServletRequest request,
                @NonNull HttpServletResponse response,
                @NonNull FilterChain filterChain) {
    
            try {
                throw new ValidationException("Invalid token");
            } catch (ValidationException e) {
                resolver.resolveException(request, response, null, e);
            }
        }
    }
    

    ValidationException

    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public class ValidationException extends RuntimeException {
        public ValidationException(String message) {
            super(message);
        }
    }
    

    Update Added my security config (relevant parts)

        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            return http
                    .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    .csrf(AbstractHttpConfigurer::disable)
                    .cors(Customizer.withDefaults())
                    .formLogin(AbstractHttpConfigurer::disable)
                    .authorizeHttpRequests(authorize -> authorize
                            .requestMatchers("/graphiql","/graphql").permitAll()
                            .requestMatchers("/error").permitAll()
                            .anyRequest().authenticated()
                    )
                    .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                    .build();
        }