Introduction to JWT Authentication
JSON Web Tokens (JWT) have become the standard for stateless authentication in modern web applications. JWT provides a compact, URL-safe way to represent claims between two parties, making it ideal for RESTful APIs and microservices architectures.
JWT Structure
A JWT consists of three parts:
- Header: Contains token type and signing algorithm
- Payload: Contains claims (user data, expiration, etc.)
- Signature: Verifies the token integrity
Project Setup and Dependencies
Maven Dependencies
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Starter Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 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> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- Database (Optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
Core JWT Components
1. JWT Utility Class
package com.example.security.jwt; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtTokenUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; // Generate secret key from the secret string private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(secret.getBytes()); } // Retrieve username from jwt token public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } // Retrieve expiration date from jwt token public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } // For retrieving any information from token we will need the secret key private Claims getAllClaimsFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); } // Check if the token has expired private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } // Generate token for user public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } // While creating the token - // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID // 2. Sign the JWT using the HS512 algorithm and secret key // 3. According to JWS Compact Serialization, compaction of the JWT to a URL-safe string private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .signWith(getSigningKey(), SignatureAlgorithm.HS512) .compact(); } // Validate token public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } // Refresh token public String refreshToken(String token) { final Date createdDate = new Date(); final Date expirationDate = calculateExpirationDate(createdDate); final Claims claims = getAllClaimsFromToken(token); claims.setIssuedAt(createdDate); claims.setExpiration(expirationDate); return Jwts.builder() .setClaims(claims) .signWith(getSigningKey(), SignatureAlgorithm.HS512) .compact(); } private Date calculateExpirationDate(Date createdDate) { return new Date(createdDate.getTime() + expiration * 1000); } } 2. Custom User Details Service
package com.example.security.service; import com.example.security.model.User; import com.example.security.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); List<SimpleGrantedAuthority> authorities = Collections.singletonList( new SimpleGrantedAuthority("ROLE_" + user.getRole()) ); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities ); } } 3. JWT Authentication Filter
package com.example.security.jwt; import com.example.security.service.CustomUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; 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; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private CustomUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // JWT Token is in the form "Bearer token" if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { logger.error("Unable to get JWT Token"); } catch (ExpiredJwtException e) { logger.warn("JWT Token has expired"); } catch (Exception e) { logger.error("JWT Token validation failed"); } } else { logger.warn("JWT Token does not begin with Bearer String"); } // Once we get the token validate it if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // If token is valid, configure Spring Security to manually set authentication if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // After setting the Authentication in the context, we specify // that the current user is authenticated SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } } Security Configuration
Spring Security Configuration
package com.example.security.config; import com.example.security.jwt.JwtAuthenticationEntryPoint; import com.example.security.jwt.JwtAuthenticationFilter; import com.example.security.service.CustomUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .cors().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .antMatchers("/api/public/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Add JWT token filter http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } JWT Authentication Entry Point
package com.example.security.jwt; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getOutputStream().println( "{ \"error\": \"" + authException.getMessage() + "\" }" ); } } Data Models and Repository
User Entity
package com.example.security.model; import javax.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private String email; @Column(nullable = false) private String role = "USER"; // Constructors public User() {} public User(String username, String password, String email, String role) { this.username = username; this.password = password; this.email = email; this.role = role; } // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } User Repository
package com.example.security.repository; import com.example.security.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); Optional<User> findByEmail(String email); Boolean existsByUsername(String username); Boolean existsByEmail(String email); } Authentication Controller
Authentication Request/Response DTOs
package com.example.security.dto; public class AuthenticationRequest { private String username; private String password; // Default constructor for JSON parsing public AuthenticationRequest() {} public AuthenticationRequest(String username, String password) { this.username = username; this.password = password; } // Getters and Setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } package com.example.security.dto; public class AuthenticationResponse { private String token; private String type = "Bearer"; private String username; private String role; public AuthenticationResponse(String token, String username, String role) { this.token = token; this.username = username; this.role = role; } // Getters and Setters public String getToken() { return token; } public void setToken(String token) { this.token = token; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } package com.example.security.dto; public class RegisterRequest { private String username; private String password; private String email; private String role = "USER"; // Constructors, Getters and Setters public RegisterRequest() {} public RegisterRequest(String username, String password, String email) { this.username = username; this.password = password; this.email = email; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } Authentication Controller
package com.example.security.controller; import com.example.security.dto.AuthenticationRequest; import com.example.security.dto.AuthenticationResponse; import com.example.security.dto.RegisterRequest; import com.example.security.jwt.JwtTokenUtil; import com.example.security.model.User; import com.example.security.repository.UserRepository; import com.example.security.service.CustomUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/auth") public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private CustomUserDetailsService userDetailsService; @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @PostMapping("/login") public ResponseEntity<?> createAuthenticationToken( @RequestBody @Valid AuthenticationRequest authenticationRequest) { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( authenticationRequest.getUsername(), authenticationRequest.getPassword() ) ); } catch (BadCredentialsException e) { Map<String, String> error = new HashMap<>(); error.put("error", "Incorrect username or password"); return ResponseEntity.badRequest().body(error); } final UserDetails userDetails = userDetailsService .loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); // Get user role User user = userRepository.findByUsername(authenticationRequest.getUsername()) .orElseThrow(() -> new RuntimeException("User not found")); return ResponseEntity.ok(new AuthenticationResponse( token, user.getUsername(), user.getRole() )); } @PostMapping("/register") public ResponseEntity<?> registerUser(@RequestBody @Valid RegisterRequest registerRequest) { // Check if username exists if (userRepository.existsByUsername(registerRequest.getUsername())) { Map<String, String> error = new HashMap<>(); error.put("error", "Username is already taken"); return ResponseEntity.badRequest().body(error); } // Check if email exists if (userRepository.existsByEmail(registerRequest.getEmail())) { Map<String, String> error = new HashMap<>(); error.put("error", "Email is already in use"); return ResponseEntity.badRequest().body(error); } // Create new user User user = new User( registerRequest.getUsername(), passwordEncoder.encode(registerRequest.getPassword()), registerRequest.getEmail(), registerRequest.getRole() ); userRepository.save(user); Map<String, String> response = new HashMap<>(); response.put("message", "User registered successfully"); return ResponseEntity.ok(response); } @PostMapping("/refresh") public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String authHeader) { if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { String refreshedToken = jwtTokenUtil.refreshToken(token); Map<String, String> response = new HashMap<>(); response.put("token", refreshedToken); return ResponseEntity.ok(response); } catch (Exception e) { Map<String, String> error = new HashMap<>(); error.put("error", "Invalid token"); return ResponseEntity.badRequest().body(error); } } Map<String, String> error = new HashMap<>(); error.put("error", "Authorization header is missing or invalid"); return ResponseEntity.badRequest().body(error); } } Protected Resources and Controllers
User Controller
package com.example.security.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/profile") @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") public ResponseEntity<?> getUserProfile() { Map<String, String> profile = new HashMap<>(); profile.put("message", "This is user profile data"); profile.put("access", "User level access granted"); return ResponseEntity.ok(profile); } @GetMapping("/dashboard") @PreAuthorize("hasRole('USER')") public ResponseEntity<?> getUserDashboard() { Map<String, String> dashboard = new HashMap<>(); dashboard.put("message", "Welcome to User Dashboard"); dashboard.put("features", "Basic features available"); return ResponseEntity.ok(dashboard); } } Admin Controller
package com.example.security.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/admin") public class AdminController { @GetMapping("/dashboard") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity<?> getAdminDashboard() { Map<String, String> dashboard = new HashMap<>(); dashboard.put("message", "Welcome to Admin Dashboard"); dashboard.put("features", "All administrative features available"); return ResponseEntity.ok(dashboard); } @GetMapping("/users") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity<?> getAllUsers() { Map<String, String> users = new HashMap<>(); users.put("message", "List of all users (admin only)"); users.put("data", "User data would be here"); return ResponseEntity.ok(users); } } Public Controller
package com.example.security.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/public") public class PublicController { @GetMapping("/info") public ResponseEntity<?> getPublicInfo() { Map<String, String> info = new HashMap<>(); info.put("message", "This is public information"); info.put("access", "No authentication required"); return ResponseEntity.ok(info); } @GetMapping("/health") public ResponseEntity<?> healthCheck() { Map<String, String> health = new HashMap<>(); health.put("status", "UP"); health.put("service", "JWT Authentication Service"); return ResponseEntity.ok(health); } } Application Configuration
Application Properties
# application.properties # Server Configuration server.port=8080 # JWT Configuration jwt.secret=mySecretKeyWhichIsVeryLongAndSecureForJWTTokenGeneration12345 jwt.expiration=86400 # 24 hours in seconds # Database Configuration (H2 for example) spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # JPA Configuration spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true # H2 Console (for development) spring.h2.console.enabled=true
Data Initializer (Optional)
package com.example.security.config; import com.example.security.model.User; import com.example.security.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component public class DataInitializer implements CommandLineRunner { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @Override public void run(String... args) throws Exception { // Create admin user if (!userRepository.existsByUsername("admin")) { User admin = new User( "admin", passwordEncoder.encode("admin123"), "[email protected]", "ADMIN" ); userRepository.save(admin); System.out.println("Admin user created"); } // Create regular user if (!userRepository.existsByUsername("user")) { User user = new User( "user", passwordEncoder.encode("user123"), "[email protected]", "USER" ); userRepository.save(user); System.out.println("Regular user created"); } } } Testing the JWT Authentication
Testing with curl Commands
# Test public endpoint (no authentication required) curl http://localhost:8080/api/public/health # Register a new user curl -X POST http://localhost:8080/api/auth/register \ -H "Content-Type: application/json" \ -d '{"username":"testuser", "password":"test123", "email":"[email protected]"}' # Login and get JWT token curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"testuser", "password":"test123"}' # Use the token to access protected user endpoint curl http://localhost:8080/api/user/profile \ -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" # Try to access admin endpoint with user token (should fail) curl http://localhost:8080/api/admin/dashboard \ -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" # Refresh token curl -X POST http://localhost:8080/api/auth/refresh \ -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" Integration Tests
package com.example.security.test; import com.example.security.dto.AuthenticationRequest; import com.example.security.dto.RegisterRequest; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @AutoConfigureMockMvc public class JwtAuthenticationTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Test public void testPublicEndpoint() throws Exception { mockMvc.perform(get("/api/public/health")) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("UP")); } @Test public void testRegisterAndLogin() throws Exception { // Register new user RegisterRequest registerRequest = new RegisterRequest( "testuser", "password123", "[email protected]" ); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(registerRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("User registered successfully")); // Login with registered user AuthenticationRequest authRequest = new AuthenticationRequest( "testuser", "password123" ); mockMvc.perform(post("/api/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(authRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.token").exists()) .andExpect(jsonPath("$.username").value("testuser")); } @Test public void testProtectedEndpointWithoutToken() throws Exception { mockMvc.perform(get("/api/user/profile")) .andExpect(status().isUnauthorized()); } } Advanced Features
Custom JWT Claims
// Enhanced JWT Utility with custom claims public String generateTokenWithClaims(UserDetails userDetails) { User user = userRepository.findByUsername(userDetails.getUsername()) .orElseThrow(() -> new UsernameNotFoundException("User not found")); Map<String, Object> claims = new HashMap<>(); claims.put("userId", user.getId()); claims.put("email", user.getEmail()); claims.put("role", user.getRole()); claims.put("createdAt", System.currentTimeMillis()); return doGenerateToken(claims, userDetails.getUsername()); } public Long getUserIdFromToken(String token) { return getClaimFromToken(token, claims -> claims.get("userId", Long.class)); } public String getEmailFromToken(String token) { return getClaimFromToken(token, claims -> claims.get("email", String.class)); } Token Blacklisting
@Component public class TokenBlacklistService { private final Set<String> blacklistedTokens = ConcurrentHashMap.newKeySet(); public void blacklistToken(String token) { blacklistedTokens.add(token); } public boolean isTokenBlacklisted(String token) { return blacklistedTokens.contains(token); } // Clean up expired tokens periodically @Scheduled(fixedRate = 3600000) // Run every hour public void cleanupExpiredTokens() { // Implementation to remove expired tokens from blacklist } } Security Best Practices
- Use strong secret keys and rotate them regularly
- Set appropriate token expiration times
- Use HTTPS in production
- Implement proper password hashing (BCrypt)
- Add rate limiting for authentication endpoints
- Use secure cookie settings if storing tokens in cookies
- Implement proper CORS configuration
- Add logging and monitoring for security events
- Consider using refresh token rotation
- Implement proper error handling without information leakage
This comprehensive JWT authentication implementation provides a solid foundation for securing your Spring Boot applications with stateless, scalable authentication using JSON Web Tokens.