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