JWT Authentication
IntermediateJWT is a stateless authentication token — the server signs it, clients store and send it. A OncePerRequestFilter validates the token on every request and populates the SecurityContext without any database lookup.
Overview
A JWT has three parts: Header (algorithm), Payload (claims: sub, iat, exp, custom), Signature (HMAC or RSA). The server signs the token with a secret key; clients include it in the Authorization: Bearer header. A servlet filter (OncePerRequestFilter) intercepts each request, validates the token signature and expiry, and sets the Authentication in SecurityContextHolder. No session, no database lookup — purely stateless.
JWT Service — Generate and Validate Tokens
Use the JJWT library for JWT creation and parsing. Sign with HMAC-SHA256 using a secret key of at least 256 bits. Store the secret in an environment variable, never in code.
<!-- pom.xml -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
@Service
public class JwtService {
@Value("${app.jwt.secret}")
private String secret;
@Value("${app.jwt.expiry-minutes:60}")
private int expiryMinutes;
private SecretKey signingKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(String userId) {
return Jwts.builder()
.subject(userId)
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plusSeconds(expiryMinutes * 60L)))
.signWith(signingKey())
.compact();
}
public String extractUserId(String token) {
return parseClaims(token).getSubject();
}
public boolean isValid(String token) {
try {
parseClaims(token); // throws on invalid/expired
return true;
} catch (JwtException e) {
return false;
}
}
private Claims parseClaims(String token) {
return Jwts.parser()
.verifyWith(signingKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
}JWT Filter — Authenticate Every Request
OncePerRequestFilter guarantees exactly one execution per request. It extracts the token from the Authorization header, validates it, and sets Authentication in SecurityContextHolder so downstream code knows the user is authenticated.
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
// No token — pass through (let security rules handle it)
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = header.substring(7); // strip "Bearer "
if (jwtService.isValid(token)
&& SecurityContextHolder.getContext().getAuthentication() == null) {
String userId = jwtService.extractUserId(token);
// Set authentication — no DB lookup needed
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
userId, // principal — available via @AuthenticationPrincipal
null, // credentials — not needed post-auth
List.of(new SimpleGrantedAuthority("ROLE_USER"))
);
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
// Register in SecurityConfig
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)Key Points to Remember
- 1JWT = Header.Payload.Signature — the server signs with a secret key; clients cannot forge tokens.
- 2JWTs are stateless — no database lookup required for validation, just signature verification.
- 3OncePerRequestFilter ensures the JWT filter runs exactly once per request, not once per servlet dispatch.
- 4Set Authentication in SecurityContextHolder after validation — this is how Spring Security knows the user is logged in.
- 5Use a secret key of at least 256 bits for HMAC-SHA256 signing — generate with Keys.secretKeyFor(SignatureAlgorithm.HS256).
- 6Short expiry (15–60 min) + refresh tokens is more secure than long-lived JWTs — invalidation is impossible without a blocklist.
Interview Questions
Sign in to ask AriaWhat are the three parts of a JWT and what does each contain?
How does the server validate a JWT without hitting the database?
How do you invalidate a JWT before it expires?
Why use OncePerRequestFilter instead of implementing Filter directly?
What is the risk of storing a JWT in localStorage vs HttpOnly cookie?
Ask Aria about JWT Authentication
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.