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