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 < dependencies
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-security </ artifactId
</ dependency
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-web </ artifactId
</ dependency
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-data-jpa </ artifactId
</ dependency
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-validation </ artifactId
</ dependency
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-oauth2-resource-server </ artifactId
</ dependency
< dependency
< groupId
io.jsonwebtoken </ groupId
< artifactId
jjwt-api </ artifactId
< version
0.12.6 </ version
</ dependency
< dependency
< groupId
io.jsonwebtoken </ groupId
< artifactId
jjwt-impl </ artifactId
< version
0.12.6 </ version
< scope
runtime </ scope
</ dependency
< dependency
< groupId
io.jsonwebtoken </ groupId
< artifactId
jjwt-jackson </ artifactId
< version
0.12.6 </ version
< scope
runtime </ scope
</ dependency
< dependency
< groupId
com.h2database </ groupId
< artifactId
h2 </ artifactId
< scope
runtime </ scope
</ dependency
< dependency
< groupId
org.postgresql </ groupId
< artifactId
postgresql </ artifactId
< scope
runtime </ scope
</ dependency
< dependency
< groupId
org.springframework.boot </ groupId
< artifactId
spring-boot-starter-test </ artifactId
< scope
test </ scope
</ dependency
< dependency
< groupId
org.springframework.security </ groupId
< artifactId
spring-security-test </ artifactId
< scope
test </ scope
</ dependency
< dependency
< groupId
org.testcontainers </ groupId
< artifactId
junit-jupiter </ artifactId
< scope
test </ scope
</ dependency
< dependency
< groupId
org.springframework.security </ groupId
< artifactId
spring-security-test </ artifactId
< scope
test </ scope
</ dependency
</ dependencies
Gradle 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
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
- @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
- @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
- <
- String
- >
- getAuthorities
- (
- UserDetails
- userDetails
- )
- {
- return
- userDetails
- .
- getAuthorities
- (
- )
- .
- stream
- (
- )
- .
- map
- (
- GrantedAuthority
- ::
- getAuthority
- )
- .
- collect
- (
- Collectors
- .
- toList
- (
- )
- )
- ;
- }
- }
- 3. JWT Authentication Filter
- @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
- @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
- @RestController
- @RequestMapping
- (
- "/api/v1/auth"
- )
- @RequiredArgsConstructor
- @Slf4j
- public
- class
- AuthenticationController
- {
- private
- final
- AuthenticationService
- authenticationService
- ;
- @PostMapping
- (
- "/register"
- )
- public
- ResponseEntity
- <
- AuthenticationResponse
- >
- register
- (
- @Valid
- @RequestBody
- RegisterRequest
- request
- )
- {
- log
- .
- info
- (
- "Registering new user: {}"
- ,
- request
- .
- getEmail
- (
- )
- )
- ;
- return
- ResponseEntity
- .
- ok
- (
- authenticationService
- .
- register
- (
- request
- )
- )
- ;
- }
- @PostMapping
- (
- "/authenticate"
- )
- public
- ResponseEntity
- <
- AuthenticationResponse
- >
- 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
- <
- AuthenticationResponse
- >
- refreshToken
- (
- @RequestBody
- RefreshTokenRequest
- request
- )
- {
- log
- .
- info
- (
- "Refreshing token for user"
- )
- ;
- return
- ResponseEntity
- .
- ok
- (
- authenticationService
- .
- refreshToken
- (
- request
- )
- )
- ;
- }
- @GetMapping
- (
- "/me"
- )
- public
- ResponseEntity
- <
- UserProfile
- >
- 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)
- @RestController
- @RequestMapping
- (
- "/api/v1/admin"
- )
- @RequiredArgsConstructor
- @PreAuthorize
- (
- "hasRole('ADMIN')"
- )
- public
- class
- AdminController
- {
- private
- final
- AdminService
- adminService
- ;
- @GetMapping
- (
- "/users"
- )
- @PreAuthorize
- (
- "hasAuthority('ADMIN_READ')"
- )
- public
- ResponseEntity
- <
- Page
- <
- UserResponse
- >
- >
- getAllUsers
- (
- Pageable
- pageable
- )
- {
- return
- ResponseEntity
- .
- ok
- (
- adminService
- .
- getAllUsers
- (
- pageable
- )
- )
- ;
- }
- @DeleteMapping
- (
- "/users/{id}"
- )
- @PreAuthorize
- (
- "hasAuthority('ADMIN_DELETE')"
- )
- public
- ResponseEntity
- <
- Void
- >
- deleteUser
- (
- @PathVariable
- Long
- id
- )
- {
- adminService
- .
- deleteUser
- (
- id
- )
- ;
- return
- ResponseEntity
- .
- noContent
- (
- )
- .
- build
- (
- )
- ;
- }
- @PostMapping
- (
- "/users/{id}/roles"
- )
- @PreAuthorize
- (
- "hasAuthority('ADMIN_MANAGE_ROLES')"
- )
- public
- ResponseEntity
- <
- UserResponse
- >
- assignRole
- (
- @PathVariable
- Long
- id
- ,
- @Valid
- @RequestBody
- AssignRoleRequest
- request
- )
- {
- return
- ResponseEntity
- .
- ok
- (
- adminService
- .
- assignRole
- (
- id
- ,
- request
- )
- )
- ;
- }
- }
- Permission-Based Access Control
- @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
- @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
- @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
- ;
- @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
- <
- RefreshToken
- >
- 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
- @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
- (
- )
- .
- (
- "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
- @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
- @Service
- @RequiredArgsConstructor
- public
- class
- TokenBlacklistService
- {
- private
- final
- RedisTemplate
- <
- string
- ,
- string
- >
- 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 // 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 // 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 @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 { "email" : "user@example.com" , "password" : "SecurePass123!" } Output: Authentication Response { "accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." , "refreshToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." , "tokenType" : "Bearer" , "expiresIn" : 86400 , "user" : { "id" : 1 , "email" : "user@example.com" , "role" : "USER" } } Input: Registration Request { "username" : "newuser" , "email" : "newuser@example.com" , "password" : "SecurePass123!" , "confirmPassword" : "SecurePass123!" } Output: Registration Response { "id" : 2 , "username" : "newuser" , "email" : "newuser@example.com" , "role" : "USER" , "createdAt" : "2024-01-15T10:30:00Z" } Input: Protected API Request Without Token curl -X GET http://localhost:8080/api/v1/orders Output: 401 Unauthorized { "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 curl -X GET http://localhost:8080/api/v1/orders \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." Output: 200 OK { "content" : [ { "id" : 1 , "orderNumber" : "ORD-001" , "status" : "COMPLETED" , "total" : 99.99 } ] , "pageable" : { "page" : 0 , "size" : 20 , "total" : 1 } } Input: Refresh Token Request { "refreshToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } Output: New Access Token { "accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." , "refreshToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." , "tokenType" : "Bearer" , "expiresIn" : 86400 } Reference Materials Complete JWT Configuration Guide - Consolidated configuration patterns for Spring Security 6.x JWT Testing Guide - Comprehensive testing strategies JWT Quick Reference - Common patterns and quick examples Complete implementation examples Security hardening checklist Migration guide for Spring Security 6.x