Stateless JWT Authentication Filter and SecurityFilterChain in Spring Boot

Shared by codesnips Jul 2026
3 tabs
package com.example.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;

@Component
public class JwtTokenProvider {

    private final SecretKey key;

    public JwtTokenProvider(@Value("${security.jwt.secret}") String secret) {
        this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException ex) {
            return false;
        }
    }

    public String getUsername(String token) {
        Claims claims = Jwts.parser().verifyWith(key).build()
                .parseSignedClaims(token)
                .getPayload();
        return claims.getSubject();
    }

    public String getRole(String token) {
        Claims claims = Jwts.parser().verifyWith(key).build()
                .parseSignedClaims(token)
                .getPayload();
        return claims.get("role", String.class);
    }
}
3 files · java Explain with highlit

This snippet shows how a stateless JWT authentication layer is wired into Spring Security, replacing the default session-based flow with a per-request token check. The core idea is that each protected request carries a signed bearer token in the Authorization header; instead of the server keeping session state, it validates the signature and expiry on every call and populates the SecurityContext for the duration of that request only. This is the standard pattern for horizontally-scalable REST APIs where sticky sessions are undesirable.

In JwtTokenProvider, the token concerns are isolated behind a small service. It builds a signing SecretKey from a base64-encoded secret and uses the jjwt library to parseSignedClaims. The validateToken method deliberately catches JwtException and IllegalArgumentException and returns false rather than propagating — malformed, tampered, or expired tokens are treated as simply unauthenticated, not as server errors. Keeping the secret and parser configuration in one place means the filter stays thin and the signing algorithm can be changed without touching request-handling logic.

In JwtAuthenticationFilter, the class extends OncePerRequestFilter so it runs exactly once per request even when the servlet container internally forwards or includes. The filter extracts the token via resolveToken, checking for the Bearer prefix. When a valid token is present, it constructs a UsernamePasswordAuthenticationToken with the subject and authorities and stores it in the SecurityContextHolder. A crucial detail is that the filter never blocks the chain itself: if there is no token or the token is invalid, it simply calls filterChain.doFilter without setting authentication, and downstream authorization rules decide whether the request is allowed. This separation keeps the filter reusable across public and protected routes.

In SecurityConfig, the SecurityFilterChain bean expresses policy declaratively. CSRF is disabled because there are no cookies or server sessions to protect, and SessionCreationPolicy.STATELESS guarantees Spring never creates an HttpSession. The authorizeHttpRequests block whitelists /api/auth/** and the actuator health endpoint as permitAll, while everything else requires authentication. The custom filter is registered with addFilterBefore ahead of UsernamePasswordAuthenticationFilter, so the JWT check happens before the framework's default username/password machinery would run. An AuthenticationEntryPoint returns a clean 401 for unauthenticated access rather than redirecting to a login page.

A common pitfall this design avoids is leaking authentication state between requests: because the context is per-request and the session policy is stateless, there is no risk of one user's identity bleeding into another's request on a reused thread. The trade-off is that token revocation is not immediate — a valid token stays valid until it expires — which is typically mitigated with short lifetimes and a refresh-token flow. Developers reach for this pattern when building token-secured APIs consumed by SPAs or mobile clients that cannot rely on cookies.

Share this code

Here's the card — post it anywhere.

Stateless JWT Authentication Filter and SecurityFilterChain in Spring Boot — share card
Link copied