Java Notes
Implementing JWT authentication in Spring Boot - token generation, validation, refresh tokens, security filter, and complete authentication flow.
What is JWT?
JWT (JSON Web Token) is a compact, URL-safe token format used for stateless authentication. It eliminates the need for server-side session storage.
JWT Structure
| Part | Content |
|---|---|
| Header | Algorithm (HS256, RS256) + token type |
| Payload | Claims (sub, exp, iat, roles, custom data) |
| Signature | HMAC or RSA signature for verification |
Setup
<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>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>JWT Service
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration}")
private long accessTokenExpiration; // e.g., 900000 (15 min)
@Value("${jwt.refresh-expiration}")
private long refreshTokenExpiration; // e.g., 604800000 (7 days)
public String generateAccessToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList());
return generateToken(claims, userDetails.getUsername(), accessTokenExpiration);
}
public String generateRefreshToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails.getUsername(), refreshTokenExpiration);
}
private String generateToken(Map<String, Object> claims, String subject, long expiration) {
return Jwts.builder()
.claims(claims)
.subject(subject)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}JWT Authentication Filter
Security Configuration with JWT
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}Auth Controller
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authManager;
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody @Valid RegisterRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
return ResponseEntity.badRequest().body(Map.of("error", "Email already exists"));
}
User user = User.builder()
.name(request.getName())
.email(request.getEmail())
.password(passwordEncoder.encode(request.getPassword()))
.role(Role.USER)
.build();
userRepository.save(user);
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getEmail());
String accessToken = jwtService.generateAccessToken(userDetails);
String refreshToken = jwtService.generateRefreshToken(userDetails);
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken,
"message", "Registration successful"
));
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
authManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail());
String accessToken = jwtService.generateAccessToken(userDetails);
String refreshToken = jwtService.generateRefreshToken(userDetails);
return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken
));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "Invalid email or password"));
}
}
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody Map<String, String> request) {
String refreshToken = request.get("refreshToken");
String username = jwtService.extractUsername(refreshToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(refreshToken, userDetails)) {
String newAccessToken = jwtService.generateAccessToken(userDetails);
return ResponseEntity.ok(Map.of("accessToken", newAccessToken));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "Invalid refresh token"));
}
}application.properties
jwt.secret=dGhpcyBpcyBhIHZlcnkgc2VjdXJlIHNlY3JldCBrZXkgZm9yIGp3dA==
jwt.expiration=900000
jwt.refresh-expiration=604800000Interview Key Points
- JWT = Header.Payload.Signature (Base64URL encoded)
- JWT is stateless — no server-side session storage needed
- Access tokens are short-lived (15 min); refresh tokens are long-lived (7 days)
OncePerRequestFilterensures JWT filter runs once per request- JWT filter extracts token → validates → sets SecurityContext
SecurityContextHolderholds current authenticated user- Never store sensitive data in JWT payload (it's Base64 encoded, not encrypted)
- Use HTTPS always — JWT can be stolen if transmitted over HTTP
Summary
In this chapter, we learned about JWT Authentication in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for JWT Authentication — Java Programming.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, spring, framework, boot
Related Java Master Course Topics