JwtAuthenticationFilter.java

package com.wilzwert.myjobs.infrastructure.security.jwt;

import com.wilzwert.myjobs.infrastructure.security.model.JwtToken;
import com.wilzwert.myjobs.infrastructure.security.service.CookieService;
import com.wilzwert.myjobs.infrastructure.security.service.JwtService;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Optional;

/**
 * @author Wilhelm Zwertvaegher
 */
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;

    private final UserDetailsService userDetailsService;

    private final CookieService cookieService;

    public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService, CookieService cookieService) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
        this.cookieService = cookieService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            Optional<JwtToken> token = jwtService.extractTokenFromRequest(request);
            if(token.isPresent()) {
                // extract JWT Token with embedded email and try to authenticate the user
                JwtToken jwtToken = token.get();
                String uid = jwtToken.getClaims().getSubject();
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (uid != null && authentication == null) {
                    // actually our user details services is based on the user id, not the username
                    // because username is subject to changes
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(uid);
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails,
                            null,
                            userDetails.getAuthorities()
                    );

                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // pass authentication to the security context
                    log.info("Token handled, set security context authentication");
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
            // security filter chain continues
            filterChain.doFilter(request, response);
        }
        catch (UsernameNotFoundException | JwtException | IllegalArgumentException e) {
            log.warn("JWT authentication filter : token or user exception {}", e.getMessage());
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().print("auth_error");
            response.addHeader(HttpHeaders.SET_COOKIE, cookieService.revokeAccessTokenCookie().toString());
            response.addHeader(HttpHeaders.SET_COOKIE, cookieService.revokeRefreshTokenCookie().toString());
        }
    }

    @Override
    public boolean shouldNotFilter(@NonNull HttpServletRequest request) {
        // bypass filter if path should remain publicly accessible
        String path = request.getRequestURI();
        return path.matches("/api/auth/(login|register|refresh-token)")
                // || path.matches(apiDocProperties.getApiDocsPath()+"/?.*")
                // || path.matches(apiDocProperties.getSwaggerPath()+"/?.*")
                || path.matches("/swagger-ui/.*")
                || path.matches("/internal/.*");
    }
}