Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the foxiz-core domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/posttrau/public_html/mdtWordpress/wp-includes/functions.php on line 6114
How to catch ExpiredJwtException in Spring OncePerRequestFilter - My Day To-Do

In this post, you will see how to catch ExpiredJwtException in OncePerRequestFilter class and send a meaningful error message from a Spring Boot app .

Background

I came across this issue after I implemented JWT authentication in the file-sharing-app. As the JWT token functionality was being tested, I realised that there is an exception being thrown due to JWT expiry. The first thing I tried was to implement the GlobalExceptionHandler and define an ExceptionHandler for ExpiredJwtException. That however, did not work but after some time, I discovered another solution, that I will be sharing next.

Catch ExpiredJwtException in doFilterInternal

If you have added Jwt authentication to your app, then you would have added a class that extends the OncePerRequestFilter to filters requests by validating the token sent with it. Subsequently, you would also have a service class that implements functionalities such as createToken, method to extract claims etc. Now, you need to include the code in the doFilterInternal within a try block and catch the ExpiredJwtException and write the meaningful error in the response. Observe the code below taken from the file-sharing-app.

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ExpiredJwtException {
        String authHeader = request.getHeader("Authorization");
        String token = null;
        String username = null;
        try {
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                token = authHeader.substring(7);
                username = jwtService.extractUsername(token);
            }

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
               UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.validateToken(token, userDetails)) {
                    log.info("JWT token validated for expiry and about to set session context");
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    log.info("The auth token looks like, {}", authToken.toString());
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
            filterChain.doFilter(request, response);

        } catch (ExpiredJwtException expiredJwtException) {
            log.info("In the expired JWT Exception handler");
            response.setContentType("application/json");
            response.setStatus(HttpStatus.FORBIDDEN.value());
            ObjectMapper mapper = new ObjectMapper();
            String exMessage = mapper.writeValueAsString(expiredJwtException(expiredJwtException.getMessage()));
            response.getWriter().write(exMessage);
        }
    }

You can find the full source code for the file here.

Or for convenience I can include it all here,

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mydaytodo.sfa.asset.model.ServiceResponse;
import com.mydaytodo.sfa.asset.service.JwtService;
import com.mydaytodo.sfa.asset.service.UserAuthServiceImpl;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

// This class helps us to validate the generated jwt token
@Component
@Slf4j
public class JwtAuthFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Autowired
    private UserAuthServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ExpiredJwtException {
        String authHeader = request.getHeader("Authorization");
        String token = null;
        String username = null;
        try {
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                token = authHeader.substring(7);
                username = jwtService.extractUsername(token);
            }

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
               UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.validateToken(token, userDetails)) {
                    log.info("JWT token validated for expiry and about to set session context");
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    log.info("The auth token looks like, {}", authToken.toString());
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
            filterChain.doFilter(request, response);

        } catch (ExpiredJwtException expiredJwtException) {
            log.info("In the expired JWT Exception handler");
            response.setContentType("application/json");
            response.setStatus(HttpStatus.FORBIDDEN.value());
            ObjectMapper mapper = new ObjectMapper();
            String exMessage = mapper.writeValueAsString(expiredJwtException(expiredJwtException.getMessage()));
            response.getWriter().write(exMessage);
        }
    }
    private ServiceResponse expiredJwtException(String message) {
        return ServiceResponse.builder()
                .data("Token expired, please logout and login again")
                .message(message)
                .status(HttpStatus.FORBIDDEN.value())
                .build();
    }
}

Conclusion

If you have any questions on this, feel free to leave a comment on this post or send me an email directly.

If you find any of my posts useful and want to support me, you can buy me a coffee 🙂

https://www.buymeacoffee.com/bhumansoni

While you are here, maybe try one of my apps for the iPhone.

Products – My Day To-Do (mydaytodo.com)

Here are some of my other bloposts on Java

How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com)

How to call REST API with WebClient – My Day To-Do (mydaytodo.com)

How to build a jokes client in Java Spring Boot with RestTemplate – My Day To-Do (mydaytodo.com)

Have a read of some of my other posts on AWS

Upload to AWS S3 bucket from Java Spring Boot app – My Day To-Do (mydaytodo.com)

Deploy NodeJS, Typescript app on AWS Elastic beanstalk – (mydaytodo.com)

How to deploy spring boot app to AWS & serve via https – My Day To-Do (mydaytodo.com)


0 Comments

Leave a Reply

Avatar placeholder
Verified by MonsterInsights