---
name: spring-boot-security-jwt
description: Provides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
category: backend
tags: [spring-boot, spring-security, jwt, authentication, authorization, rbac, oauth2, jjwt]
version: 1.0.0
---
# Spring Boot JWT Security
Comprehensive JWT (JSON Web Token) authentication and authorization patterns for Spring Boot 3.5.x applications using Spring Security 6.x and the JJWT library. This skill provides production-ready implementations for stateless authentication, role-based access control, and integration with modern authentication providers.
## Overview
JWT authentication enables stateless, scalable security for Spring Boot applications. This skill covers complete JWT lifecycle management including token generation, validation, refresh strategies, and integration patterns with database-backed and OAuth2 authentication providers. Implementations follow Spring Security 6.x best practices with modern SecurityFilterChain configuration.
## When to Use
Use this skill when:
- Implementing stateless authentication for REST APIs
- Building SPA (Single Page Application) backends with JWT
- Securing microservices with token-based authentication
- Integrating with OAuth2 providers (Google, GitHub, etc.)
- Implementing role-based or permission-based access control
- Setting up JWT refresh token strategies
- Migrating from session-based to token-based authentication
- Building mobile API backends
- Implementing cross-origin authentication with CORS
## Prerequisites
- Java 17+ (for records and pattern matching)
- Spring Boot 3.5.x (for Spring Security 6.x integration)
- JJWT library (io.jsonwebtoken) for JWT operations
- Maven or Gradle build system
- Basic understanding of Spring Security concepts
## Dependencies
### Maven
```xml
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-oauth2-resource-server
io.jsonwebtoken
jjwt-api
0.12.6
io.jsonwebtoken
jjwt-impl
0.12.6
runtime
io.jsonwebtoken
jjwt-jackson
0.12.6
runtime
com.h2database
h2
runtime
org.postgresql
postgresql
runtime
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
org.testcontainers
junit-jupiter
test
org.springframework.security
spring-security-test
test
```
### Gradle
```kotlin
dependencies {
// Spring Security
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
// JWT Library
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
implementation("io.jsonwebtoken:jjwt-impl:0.12.6")
implementation("io.jsonwebtoken:jjwt-jackson:0.12.6")
// Database
runtimeOnly("com.h2database:h2")
runtimeOnly("org.postgresql:postgresql")
// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.testcontainers:junit-jupiter")
}
```
## Instructions
Follow these steps to implement JWT authentication in Spring Boot:
### 1. Add Dependencies
Include spring-boot-starter-security, spring-boot-starter-oauth2-resource-server, and JJWT library (jjwt-api, jjwt-impl, jjwt-jackson) in your project.
### 2. Configure JWT Properties
Set JWT secret, access token expiration, refresh token expiration, and issuer in application.yml. Never hardcode secrets in version control.
### 3. Create JWT Service
Implement JwtService with methods to generate tokens, extract claims, validate tokens, and check expiration. Use Jwts.builder() for token creation.
### 4. Implement JWT Filter
Create JwtAuthenticationFilter extending OncePerRequestFilter. Extract JWT from Authorization header or cookie, validate it, and set SecurityContext authentication.
### 5. Configure Security Filter Chain
Set up SecurityConfig with @EnableWebSecurity and @EnableMethodSecurity. Configure stateless session management, CSRF disabled, and authorization rules.
### 6. Create Authentication Endpoints
Implement /register, /authenticate, /refresh, and /logout endpoints. Return access and refresh tokens on successful authentication.
### 7. Implement Refresh Token Strategy
Store refresh tokens in database with expiration and revocation status. Implement token rotation for enhanced security.
### 8. Add Authorization Rules
Apply @PreAuthorize annotations with role-based (hasRole) or permission-based (hasAuthority) checks to protected endpoints.
### 9. Test Security Configuration
Write tests for authentication success/failure, authorization access control, and token validation scenarios.
## Quick Start
### 1. Application Configuration
```yaml
# application.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: openid, profile, email
jwt:
secret: ${JWT_SECRET:my-very-secret-key-that-is-at-least-256-bits-long}
access-token-expiration: 86400000 # 24 hours in milliseconds
refresh-token-expiration: 604800000 # 7 days in milliseconds
issuer: spring-boot-jwt-example
cookie-name: jwt-token
cookie-secure: false # Set to true in production with HTTPS
cookie-http-only: true
cookie-same-site: lax
```
### 2. Modern Spring Security 6.x Configuration
```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final LogoutHandler logoutHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/swagger-ui/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout
.logoutUrl("/api/auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response, authentication) ->
SecurityContextHolder.clearContext())
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
```
### 3. JWT Service Implementation
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class JwtService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.access-token-expiration}")
private long accessTokenExpiration;
@Value("${jwt.refresh-token-expiration}")
private long refreshTokenExpiration;
@Value("${jwt.issuer}")
private String issuer;
private final RefreshTokenService refreshTokenService;
/**
* Generate access token for user
*/
public String generateAccessToken(UserDetails userDetails) {
return generateToken(userDetails, accessTokenExpiration);
}
/**
* Generate refresh token for user
*/
public String generateRefreshToken(UserDetails userDetails) {
return refreshTokenService.createRefreshToken(userDetails.getUsername());
}
/**
* Extract username from JWT token
*/
public String extractUsername(String token) {
return extractClaims(token).getSubject();
}
/**
* Extract claims from JWT token
*/
private Claims extractClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
/**
* Validate JWT token
*/
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) &&
!isTokenExpired(token) &&
extractClaims(token).getIssuer().equals(issuer));
} catch (JwtException | IllegalArgumentException e) {
log.debug("Invalid JWT token: {}", e.getMessage());
return false;
}
}
/**
* Check if token is expired
*/
private boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
/**
* Generate token with expiration
*/
private String generateToken(UserDetails userDetails, long expiration) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuer(issuer)
.setIssuedAt(now)
.setExpiration(expiryDate)
.claim("authorities", getAuthorities(userDetails))
.claim("type", "access")
.signWith(getSigningKey())
.compact();
}
/**
* Get signing key from secret
*/
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* Extract authorities from user details
*/
private List getAuthorities(UserDetails userDetails) {
return userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
}
}
```
### 3. JWT Authentication Filter
```java
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
// Check for Bearer token
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
// Check for JWT cookie
String jwtCookie = WebUtils.getCookie(request, "jwt-token") != null
? WebUtils.getCookie(request, "jwt-token").getValue()
: null;
if (jwtCookie != null) {
jwt = jwtCookie;
userEmail = jwtService.extractUsername(jwt);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
userEmail = jwtService.extractUsername(jwt);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
```
### 4. Security Configuration
```java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final LogoutHandler logoutHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// Public endpoints
.requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers("/api/v1/oauth2/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/health").permitAll()
// Admin endpoints
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
// Protected endpoints
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.oauth2Login(oauth2 -> oauth2
.loginPage("/oauth2/authorization/google")
.defaultSuccessUrl("/api/v1/auth/oauth2/success", true)
.failureUrl("/api/v1/auth/oauth2/failure")
)
.logout(logout -> logout
.logoutUrl("/api/v1/auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response, authentication) ->
SecurityContextHolder.clearContext())
);
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
```
## Authentication Controllers
```java
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthenticationController {
private final AuthenticationService authenticationService;
@PostMapping("/register")
public ResponseEntity register(
@Valid @RequestBody RegisterRequest request) {
log.info("Registering new user: {}", request.getEmail());
return ResponseEntity.ok(authenticationService.register(request));
}
@PostMapping("/authenticate")
public ResponseEntity authenticate(
@Valid @RequestBody AuthenticationRequest request) {
log.info("Authenticating user: {}", request.getEmail());
AuthenticationResponse response = authenticationService.authenticate(request);
return ResponseEntity.ok()
.header("Set-Cookie", createJwtCookie(response.getAccessToken()))
.body(response);
}
@PostMapping("/refresh")
public ResponseEntity refreshToken(
@RequestBody RefreshTokenRequest request) {
log.info("Refreshing token for user");
return ResponseEntity.ok(authenticationService.refreshToken(request));
}
@GetMapping("/me")
public ResponseEntity getCurrentUser() {
return ResponseEntity.ok(authenticationService.getCurrentUser());
}
private String createJwtCookie(String token) {
return String.format(
"jwt-token=%s; Path=/; HttpOnly; SameSite=Lax; Max-Age=%d",
token,
86400 // 24 hours
);
}
}
```
## Authorization Patterns
### Role-Based Access Control (RBAC)
```java
@RestController
@RequestMapping("/api/v1/admin")
@RequiredArgsConstructor
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {
private final AdminService adminService;
@GetMapping("/users")
@PreAuthorize("hasAuthority('ADMIN_READ')")
public ResponseEntity> getAllUsers(Pageable pageable) {
return ResponseEntity.ok(adminService.getAllUsers(pageable));
}
@DeleteMapping("/users/{id}")
@PreAuthorize("hasAuthority('ADMIN_DELETE')")
public ResponseEntity deleteUser(@PathVariable Long id) {
adminService.deleteUser(id);
return ResponseEntity.noContent().build();
}
@PostMapping("/users/{id}/roles")
@PreAuthorize("hasAuthority('ADMIN_MANAGE_ROLES')")
public ResponseEntity assignRole(
@PathVariable Long id,
@Valid @RequestBody AssignRoleRequest request) {
return ResponseEntity.ok(adminService.assignRole(id, request));
}
}
```
### Permission-Based Access Control
```java
@Service
@RequiredArgsConstructor
public class DocumentService {
@PreAuthorize("hasPermission(#documentId, 'Document', 'READ')")
public Document getDocument(Long documentId) {
return documentRepository.findById(documentId)
.orElseThrow(() -> new DocumentNotFoundException(documentId));
}
@PreAuthorize("hasPermission(#documentId, 'Document', 'WRITE') or hasRole('ADMIN')")
public Document updateDocument(Long documentId, UpdateDocumentRequest request) {
Document document = getDocument(documentId);
document.setContent(request.content());
return documentRepository.save(document);
}
@PreAuthorize("@documentSecurityService.canAccess(#userEmail, #documentId)")
public Document shareDocument(String userEmail, Long documentId) {
// Implementation
}
}
```
### Custom Permission Evaluator
```java
@Component
@RequiredArgsConstructor
public class DocumentPermissionEvaluator implements PermissionEvaluator {
private final DocumentRepository documentRepository;
@Override
public boolean hasPermission(
Authentication authentication,
Object targetDomainObject,
Object permission) {
if (authentication == null || !(targetDomainObject instanceof Document)) {
return false;
}
Document document = (Document) targetDomainObject;
String username = authentication.getName();
String requiredPermission = (String) permission;
// Admin can do anything
if (hasRole(authentication, "ADMIN")) {
return true;
}
// Owner can read and write
if (document.getOwner().getUsername().equals(username)) {
return "READ".equals(requiredPermission) || "WRITE".equals(requiredPermission);
}
// Check shared permissions
return document.getSharedWith().stream()
.anyMatch(share -> share.getUser().getUsername().equals(username)
&& share.getPermission().name().equals(requiredPermission));
}
@Override
public boolean hasPermission(
Authentication authentication,
Serializable targetId,
String targetType,
Object permission) {
if (!"Document".equals(targetType)) {
return false;
}
Document document = documentRepository.findById((Long) targetId).orElse(null);
return document != null && hasPermission(authentication, document, permission);
}
private boolean hasRole(Authentication authentication, String role) {
return authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals("ROLE_" + role));
}
}
```
## Database Entities
```java
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Builder.Default
@Enumerated(EnumType.STRING)
private Role role = Role.USER;
@Builder.Default
private boolean enabled = true;
@Builder.Default
private boolean accountNonExpired = true;
@Builder.Default
private boolean accountNonLocked = true;
@Builder.Default
private boolean credentialsNonExpired = true;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set refreshTokens = new HashSet<>();
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@Entity
@Table(name = "refresh_tokens")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String token;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Builder.Default
private boolean revoked = false;
@Builder.Default
private boolean expired = false;
@Column(name = "expiry_date")
private LocalDateTime expiryDate;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
}
```
## Testing JWT Security
```java
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"jwt.secret=test-secret-key-for-testing-only",
"jwt.access-token-expiration=3600000"
})
class AuthenticationControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private JwtService jwtService;
@Test
void shouldAuthenticateUser() throws Exception {
AuthenticationRequest request = AuthenticationRequest.builder()
.email("test@example.com")
.password("password123")
.build();
mockMvc.perform(post("/api/v1/auth/authenticate")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.accessToken").exists())
.andExpect(jsonPath("$.refreshToken").exists())
.andExpect(jsonPath("$.user.email").value("test@example.com"));
}
@Test
void shouldDenyAccessWithoutToken() throws Exception {
mockMvc.perform(get("/api/v1/admin/users"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(roles = "ADMIN")
void shouldAllowAdminAccess() throws Exception {
mockMvc.perform(get("/api/v1/admin/users"))
.andExpect(status().isOk());
}
@Test
void shouldValidateJwtToken() throws Exception {
UserDetails userDetails = User.withUsername("test@example.com")
.password("password")
.roles("USER")
.build();
String token = jwtService.generateAccessToken(userDetails);
mockMvc.perform(get("/api/v1/auth/me")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
}
```
## Best Practices
### 1. Modern JWT Patterns
#### Key Rotation Strategy
```java
@Component
@RequiredArgsConstructor
public class JwtKeyRotationService {
private final SecretKeyRepository keyRepository;
private final CacheManager cacheManager;
@Scheduled(cron = "0 0 0 * * ?") // Daily at midnight
public void rotateKeys() {
SecretKey newKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
keyRepository.save(new SecretKeyEntity(newKey, LocalDateTime.now()));
cacheManager.getCache("jwt-keys").clear();
}
}
```
#### Token Blacklisting
```java
@Service
@RequiredArgsConstructor
public class TokenBlacklistService {
private final RedisTemplate redisTemplate;
private static final String BLACKLIST_PREFIX = "blacklist:jwt:";
public void blacklistToken(String token, long expirationTime) {
String tokenId = extractTokenId(token);
redisTemplate.opsForValue().set(
BLACKLIST_PREFIX + tokenId,
"1",
expirationTime,
TimeUnit.MILLISECONDS
);
}
public boolean isBlacklisted(String token) {
String tokenId = extractTokenId(token);
return Boolean.TRUE.equals(redisTemplate.hasKey(BLACKLIST_PREFIX + tokenId));
}
}
```
### 2. Security Configuration
- **Always use HTTPS** in production for JWT token transmission
- **Set appropriate cookie flags**: `HttpOnly`, `Secure`, `SameSite`
- **Use strong secret keys**: minimum 256 bits for HMAC algorithms
- **Implement token expiration**: Don't use tokens with infinite lifetime
- **Validate all inputs**: Never trust JWT claims without validation
- **Implement key rotation**: Regularly rotate signing keys
- **Use token blacklisting**: For logout and security incidents
### 2. Token Management
```java
// Implement refresh token rotation
public class RefreshTokenService {
@Transactional
public String rotateRefreshToken(String oldToken) {
RefreshToken refreshToken = refreshTokenRepository.findByToken(oldToken)
.orElseThrow(() -> new RefreshTokenException("Invalid refresh token"));
// Revoke old token
refreshToken.setRevoked(true);
refreshTokenRepository.save(refreshToken);
// Generate new token
return createRefreshToken(refreshToken.getUser().getUsername());
}
}
```
### 3. Performance Optimization
```java
// Cache user details to avoid database hits
@Service
@RequiredArgsConstructor
public class CachedUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final CacheManager cacheManager;
@Override
@Cacheable(value = "users", key = "#username")
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return new CustomUserDetails(user);
}
}
```
### 4. Monitoring and Audit
```java
@Component
@RequiredArgsConstructor
@Slf4j
public class SecurityAuditService {
@EventListener
public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
log.info("Authentication success for user: {}", event.getAuthentication().getName());
// Store audit event
}
@EventListener
public void handleAuthenticationFailure(AuthenticationFailureEvent event) {
log.warn("Authentication failure for user: {}", event.getAuthentication().getName());
// Store security event
}
@EventListener
public void handleAuthorizationFailure(AuthorizationFailureEvent event) {
log.warn("Authorization denied for user: {} on resource: {}",
event.getAuthentication().getName(),
event.getConfigAttributes());
}
}
```
## Constraints and Warnings
### 1. Token Size Limitations
- JWT tokens should stay under HTTP header size limits (typically 8KB)
- Avoid storing large amounts of data in JWT claims
- Use references instead of embedding complete objects
### 2. Security Considerations
- Never store sensitive information in JWT tokens
- Implement proper token revocation strategies
- Use different keys for different environments (dev, staging, prod)
- Regularly rotate signing keys
- Always use HTTPS in production environments
- Set appropriate cookie flags: `HttpOnly`, `Secure`, `SameSite`
- Use strong secret keys: minimum 256 bits for HMAC algorithms
### 3. Token Expiration
- Implement token expiration to limit the window of vulnerability
- Use short-lived access tokens with refresh token rotation
- Never use tokens with infinite lifetime
### 4. Performance Considerations
- Cache user details to avoid database hits on every request
- Consider token blacklisting for logout and security incidents
- Monitor token validation latency in production
### 5. Common Pitfalls
- Do not validate JWT signatures on the client side
- Never accept tokens from untrusted sources
- Always validate token issuer (`iss`) and audience (`aud`) claims
- Be aware that JWT claims are not encrypted, only signed
## Examples
### Input: Login Request
```json
{
"email": "user@example.com",
"password": "SecurePass123!"
}
```
### Output: Authentication Response
```json
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 86400,
"user": {
"id": 1,
"email": "user@example.com",
"role": "USER"
}
}
```
### Input: Registration Request
```json
{
"username": "newuser",
"email": "newuser@example.com",
"password": "SecurePass123!",
"confirmPassword": "SecurePass123!"
}
```
### Output: Registration Response
```json
{
"id": 2,
"username": "newuser",
"email": "newuser@example.com",
"role": "USER",
"createdAt": "2024-01-15T10:30:00Z"
}
```
### Input: Protected API Request Without Token
```bash
curl -X GET http://localhost:8080/api/v1/orders
```
### Output: 401 Unauthorized
```json
{
"timestamp": "2024-01-15T10:35:00Z",
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource"
}
```
### Input: Protected API Request With Token
```bash
curl -X GET http://localhost:8080/api/v1/orders \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```
### Output: 200 OK
```json
{
"content": [
{
"id": 1,
"orderNumber": "ORD-001",
"status": "COMPLETED",
"total": 99.99
}
],
"pageable": {
"page": 0,
"size": 20,
"total": 1
}
}
```
### Input: Refresh Token Request
```json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
### Output: New Access Token
```json
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 86400
}
```
## Reference Materials
- [Complete JWT Configuration Guide](references/jwt-complete-configuration.md) - Consolidated configuration patterns for Spring Security 6.x
- [JWT Testing Guide](references/jwt-testing-guide.md) - Comprehensive testing strategies
- [JWT Quick Reference](references/jwt-quick-reference.md) - Common patterns and quick examples
- [Complete implementation examples](references/examples.md)
- [Security hardening checklist](references/security-hardening.md)
- [Migration guide for Spring Security 6.x](references/migration-spring-security-6x.md)
## Related Skills
- `spring-boot-dependency-injection` - Constructor injection patterns used throughout
- `spring-boot-rest-api-standards` - REST API security patterns and error handling
- `unit-test-security-authorization` - Testing Spring Security configurations
- `spring-data-jpa` - User entity and repository patterns
- `spring-boot-actuator` - Security monitoring and health endpoints