google-site-verification: google61fe8ba583a51912.html
JWT Authentication in Spring Security in Java

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

  1. Use strong secret keys and rotate them regularly
  2. Set appropriate token expiration times
  3. Use HTTPS in production
  4. Implement proper password hashing (BCrypt)
  5. Add rate limiting for authentication endpoints
  6. Use secure cookie settings if storing tokens in cookies
  7. Implement proper CORS configuration
  8. Add logging and monitoring for security events
  9. Consider using refresh token rotation
  10. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper