https://github.com/denberr95/spring-lessons

Describe the bug
I am implementing an application with Spring Boot 3.4.3 and Spring Security.

I want to customize the response by implementing AccessDeniedHandler and AuthenticationEntryPoint.

Even though I have implemented the interfaces and added them to the configuration class, when I call an API with an invalid JWT token, they are never executed.

To Reproduce

  • Create a class that implements AccessDeniedHandler.
  • Create a class that implements AuthenticationEntryPoint.
  • Add the implementation:
    http.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(customAccessDeniedHandler))
  • Implement an API with a scope.
  • Call the API using an invalid JWT token (e.g., eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c).

Expected behavior

The expected behavior is:
- If the JWT token is invalid, the AuthenticationEntryPoint should be triggered to customize the response to the client.
- If the JWT token is valid but does not have the correct scope, the AccessDeniedHandler should be triggered to customize the response to the client.

Sample

SecurityConfig

@Configuration(proxyBeanMethods = false)
public class SecurityConfig {

    @Bean
    DefaultSecurityFilterChain configure(HttpSecurity http,
            CustomAccessDeniedHandler customAccessDeniedHandler,
            CustomAuthenticationEntryPoint customAuthenticationEntryPoint) throws Exception {
        http.exceptionHandling(exceptionHandling -> exceptionHandling
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                .accessDeniedHandler(customAccessDeniedHandler))
                .csrf((Customizer<CsrfConfigurer<HttpSecurity>>) CsrfConfigurer::disable)
                .sessionManagement(
                        session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(
                        jwt -> jwt.jwtAuthenticationConverter(this.jwtAuthenticationConverter())));
        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
                new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

CustomAuthenticationEntryPoint ```java @Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
            "Unauthorized: Authentication token was either missing or invalid.");
}

}


_CustomAccessDeniedHandler_
```java
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN,
                "Access Denied: Invalid grant permission to access to this resource: "
                        + request.getRequestURI());
    }
}

Reproducer: https://github.com/denberr95/spring-lessons