Implementing JWT (JSON Web Tokens) in Java is a common approach for stateless authentication in modern web applications. JWTs are compact, URL-safe tokens that can be used to securely transmit information between parties as a JSON object. Below is a step-by-step guide to implementing JWT-based authentication in a Java application using Spring Security and the Java JWT (JJWT) library.
1. What is JWT?
- A JWT consists of three parts:
- Header: Contains metadata about the token (e.g., algorithm used).
- Payload: Contains claims (e.g., user information, roles, expiration time).
- Signature: Ensures the token’s integrity.
- JWTs are signed using a secret (HMAC) or a public/private key pair (RSA or ECDSA).
2. Steps to Implement JWT in Java
Step 1: Add Dependencies
Add the JJWT library to your project.
Maven:
<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>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
Gradle:
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
Step 2: Create a JWT Utility Class
Create a utility class to handle JWT creation and validation.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class JwtUtil {
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 864_000_000; // 10 days
// Generate a JWT token
public static String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
private static String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY)
.compact();
}
// Extract username from token
public static String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// Extract expiration date from token
public static Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// Extract a specific claim from token
public static <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private static Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
}
// Validate token
public static boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
private static boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
}
Step 3: Implement a JWT Filter
Create a filter to validate JWT tokens in incoming requests.
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = JwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (JwtUtil.validateToken(jwt, username)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
Step 4: Configure Spring Security
Configure Spring Security to use the JWT filter.
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Step 5: Create an Authentication Endpoint
Create a REST endpoint to authenticate users and issue JWTs.
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@PostMapping("/authenticate")
public String authenticate(@RequestBody AuthRequest authRequest) {
// Validate username and password (e.g., using UserDetailsService)
if ("user".equals(authRequest.getUsername()) && "password".equals(authRequest.getPassword())) {
return JwtUtil.generateToken(authRequest.getUsername());
} else {
throw new RuntimeException("Invalid credentials");
}
}
}
class AuthRequest {
private String username;
private String password;
// Getters and setters
}
Step 6: Test the Implementation
- Start the application.
- Send a POST request to
/authenticate
with a JSON body:
{
"username": "user",
"password": "password"
}
- Receive a JWT in the response.
- Use the JWT in the
Authorization
header for subsequent requests:
Authorization: Bearer <JWT>
3. Best Practices
- Use HTTPS: Always use HTTPS to protect JWTs in transit.
- Set Expiration Time: Ensure JWTs have a short expiration time to minimize risks.
- Store Secrets Securely: Use environment variables or a secrets manager for the JWT secret key.
- Validate Input: Sanitize and validate all inputs to prevent injection attacks.
- Use Strong Algorithms: Use strong signing algorithms like HMAC-SHA256 or RSA.