Spring Boot JWT Authentication
JWT (JSON Web Token) is a stateless authentication method. After a user logs in, the server issues a token. The user sends this token with every request. The server verifies the token without checking a database — making it fast and scalable.
Session vs JWT Comparison
Session-based (stateful): JWT-based (stateless): ────────────────────────────────── ────────────────────────────────────── Login → server saves session ID Login → server creates JWT token Client stores session ID in cookie Client stores token (header/storage) Each request → server checks DB Each request → server verifies token Server holds state No server-side state needed Doesn't scale well Scales easily across multiple servers
JWT Token Structure
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJVU0VSIiwiZXhwIjoxNzE1MDAwMDAwfQ.abc123
──────────────────── ────────────────────────────────────────────── ──────
Header Payload Signature
Header (base64): Payload (base64):
{ {
"alg": "HS256", "sub": "alice",
"typ": "JWT" "role": "USER",
} "exp": 1715000000,
"iat": 1714996400
}
The signature verifies the token has not been tampered with. Only your server knows the secret key used to create it.
Add the JWT Library
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
JWT Utility Service
@Component
public class JwtService {
@Value("${jwt.secret}")
private String secretKey;
private static final long EXPIRATION_MS = 86400000; // 24 hours
// Generate a token for a user
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(getSignKey(), SignatureAlgorithm.HS256)
.compact();
}
// Extract username from token
public String extractUsername(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// Validate the token
public boolean isTokenValid(String token, UserDetails user) {
String username = extractUsername(token);
return username.equals(user.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return Jwts.parserBuilder().setSigningKey(getSignKey()).build()
.parseClaimsJws(token).getBody().getExpiration();
}
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}
Authentication Flow
STEP 1 — Login
Client: POST /api/auth/login
Body: { "email": "alice@test.com", "password": "pass123" }
│
▼
Server validates credentials
│
▼
Server generates JWT: eyJhbGci...
│
▼
Client receives: { "token": "eyJhbGci..." }
──────────────────────────────────────────────────────────────────────
STEP 2 — Subsequent Requests
Client: GET /api/orders
Header: Authorization: Bearer eyJhbGci...
│
▼
JwtAuthFilter extracts token from header
│
▼
JwtService.extractUsername(token) → "alice"
│
▼
Load UserDetails from DB
│
▼
JwtService.isTokenValid(token, userDetails) → true
│
▼
Set authentication in SecurityContext
│
▼
OrderController.getOrders() runs
JWT Filter
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired private JwtService jwtService;
@Autowired private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(7); // Remove "Bearer "
String username = jwtService.extractUsername(token);
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails =
userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
Register the Filter in SecurityConfig
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
Summary
- JWT is stateless — the server stores no session data
- A JWT token contains a header, payload, and signature
- The server issues a token on login; the client sends it with every request
- A custom filter reads the token from the
Authorizationheader, validates it, and sets the security context - Store the secret key in environment variables — never in source code
