Unable to retrieve the correct message in @GlobalHandler

17 hours ago 1
ARTICLE AD BOX

The reason you are seeing /error as the path and a generic message is that by the time your AuthenticationEntryPoint hands the exception to the HandlerExceptionResolver, the request has already been "dispatched" to the error page by the underlying container, or the WebRequest context has been lost/wrapped.

Specifically, when a Filter (where Security lives) throws an exception that isn't caught early, Spring Boot's BasicErrorController takes over, causing a forward to /error.

Here is how to fix this to ensure your @ExceptionHandler gets the original request details.


1. Fix the AuthenticationEntryPoint

The HandlerExceptionResolver needs to know which controller to target. By passing null as the handler, it sometimes struggles to map the context correctly. More importantly, we should ensure the request hasn't been modified.

Update your commence method to use the HandlerExceptionResolver more explicitly:

Java

@Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // This tells Spring to treat this exception as if it happened inside a Controller resolver.resolveException(request, response, null, authException); }

2. Update the @ExceptionHandler

When an exception occurs in a Filter, the WebRequest might point to the error dispatch path. You can extract the original URI from the HttpServletRequest attributes which Spring fills during an error dispatch.

Java

@ExceptionHandler({AuthenticationException.class, JwtException.class}) @ResponseStatus(HttpStatus.UNAUTHORIZED) public ErrorResponse handleUnAuthorizedException(Exception e, HttpServletRequest request) { // Check if there is a forwarded URI (the original request) String path = (String) request.getAttribute("jakarta.servlet.error.request_uri"); if (path == null) { path = request.getRequestURI(); } return ErrorResponse.builder() .message(e.getMessage()) // This will now catch "User not verified" .path(path) .timestamp(new Date()) .build(); }

3. Handle the Exception Type

In your stack trace, the root cause is a JwtException. While Spring Security wraps this in an AuthenticationException eventually, adding JwtException.class to your @ExceptionHandler (as shown above) ensures the resolver picks up the specific message "User not verified" rather than the generic wrapper.


Why this happens

The Failure: Your CustomJWTDecoder throws a JwtException.

The Wrapper: The JwtAuthenticationProvider catches this and throws an InsufficientAuthenticationException (a subclass of AuthenticationException).

The Entry Point: Your CustomAuthenticationEntryPoint is triggered.

The Forward: If the HandlerExceptionResolver doesn't immediately commit a response, the Servlet container sees an unhandled error and forwards to /error.

The Resolution: By using request.getAttribute("jakarta.servlet.error.request_uri"), you look past the forward to see where the user actually tried to go.

One final check

Ensure that your PUBLIC_ENDPOINT includes the path /error. If the error path itself is protected by Spring Security, the AuthenticationEntryPoint might trigger a second time, creating a loop or masking the original error message.

Java

req.requestMatchers("/error").permitAll() // Add this

Would you like me to show you how to customize the JwtAuthenticationConverter to handle this logic before it even reaches the EntryPoint?

Read Entire Article