Spring Boot JWT Security
JWT authentication and authorization patterns for Spring Boot 3.5.x using Spring Security 6.x and JJWT. Covers token generation, validation, refresh strategies, RBAC/ABAC, and OAuth2 integration.
Overview
This skill provides implementation patterns for stateless JWT authentication in Spring Boot applications. It covers the complete authentication flow including token generation with JJWT 0.12.6, Bearer/cookie-based authentication, refresh token rotation, and method-level authorization with @PreAuthorize expressions.
Key capabilities:
- Access and refresh token generation with configurable expiration
- Bearer token and HttpOnly cookie authentication strategies
- Integration with Spring Data JPA and OAuth2 providers
- RBAC with role/permission-based
@PreAuthorize rules
- Token revocation and blacklisting for logout/rotation
When to Use
Activate when user requests involve:
- "Implement JWT authentication", "secure REST API with tokens"
- "Spring Security 6.x configuration", "SecurityFilterChain setup"
- "Role-based access control", "RBAC",
`@PreAuthorize`
- "Refresh token", "token rotation", "token revocation"
- "OAuth2 integration", "social login", "Google/GitHub auth"
- "Stateless authentication", "SPA backend security"
- "JWT filter", "OncePerRequestFilter", "Bearer token"
- "Cookie-based JWT", "HttpOnly cookie"
- "Permission-based access control", "custom PermissionEvaluator"
Quick Reference
Dependencies (JJWT 0.12.6)
| Artifact |
Scope |
spring-boot-starter-security |
compile |
spring-boot-starter-oauth2-resource-server |
compile |
io.jsonwebtoken:jjwt-api:0.12.6 |
compile |
io.jsonwebtoken:jjwt-impl:0.12.6 |
runtime |
io.jsonwebtoken:jjwt-jackson:0.12.6 |
runtime |
spring-security-test |
test |
See references/jwt-quick-reference.md for Maven and Gradle snippets.
Key Configuration Properties
| Property |
Example Value |
Notes |
jwt.secret |
${JWT_SECRET} |
Min 256 bits, never hardcode |
jwt.access-token-expiration |
900000 |
15 min in milliseconds |
jwt.refresh-token-expiration |
604800000 |
7 days in milliseconds |
jwt.issuer |
my-app |
Validated on every token |
jwt.cookie-name |
jwt-token |
For cookie-based auth |
jwt.cookie-http-only |
true |
Always true in production |
jwt.cookie-secure |
true |
Always true with HTTPS |
Authorization Annotations
| Annotation |
Example |
@PreAuthorize("hasRole('ADMIN')") |
Role check |
@PreAuthorize("hasAuthority('USER_READ')") |
Permission check |
@PreAuthorize("hasPermission(#id, 'Doc', 'READ')") |
Domain object check |
@PreAuthorize("@myService.canAccess(#id)") |
Spring bean check |
Instructions
Step 1 β Add Dependencies
Include spring-boot-starter-security, spring-boot-starter-oauth2-resource-server, and the three JJWT artifacts in your build file. See references/jwt-quick-reference.md for exact Maven/Gradle snippets.
Step 2 β Configure application.yml
jwt:
secret: ${JWT_SECRET:change-me-min-32-chars-in-production}
access-token-expiration: 900000
refresh-token-expiration: 604800000
issuer: my-app
cookie-name: jwt-token
cookie-http-only: true
cookie-secure: false
See references/jwt-complete-configuration.md for the full properties reference.
Step 3 β Implement JwtService
Core operations: generate access token, generate refresh token, extract username, validate token.
@Service
public class JwtService {
public String generateAccessToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.issuer(issuer)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + accessTokenExpiration))
.claim("authorities", getAuthorities(userDetails))
.signWith(getSigningKey())
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
} catch (JwtException e) {
return false;
}
}
}
See references/jwt-complete-configuration.md for the complete JwtService including key management and claim extraction.
Step 4 β Create JwtAuthenticationFilter
Extend OncePerRequestFilter to extract a JWT from the Authorization: Bearer header (or HttpOnly cookie), validate it, and set the SecurityContext.
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}
See references/configuration.md for the cookie-based variant.
Step 5 β Configure SecurityFilterChain
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.