Spring Security 6 JWT Configuration
Problem Statement
When upgrading to Spring Security 6, developers face two significant deprecations:
- JWT Configuration: The direct
OAuth2ResourceServerConfigurer::jwt
method reference is deprecated and no longer functional - Endpoint Matchers: Traditional security matchers like
antMatchers()
,mvcMatchers()
, andregexMatchers()
have been fully removed
This migration requires adopting Spring Security's new lambda-based DSL for configuring both resource server JWT authentication and endpoint security rules. Attempting to use the deprecated approaches will result in compilation errors and runtime failures.
Solution Overview
Migration Approach
To resolve these issues:
- Replace method references with explicit lambda DSL configuration
- Use
requestMatchers()
for endpoint security - Configure JWT through the new
Customizer
interface
Key principles driving these changes:
- Type-Safe Configuration: Prevent common misconfiguration errors
- Consistent DSL: Unified approach for all security configurations
- Modern Java Practices: Leverage functional interfaces and lambdas
Endpoint Security Configuration
Replacing antMatchers
Traditional matchers are replaced by the versatile requestMatchers()
method:
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // New replacement
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
JWT Resource Server Configuration
Recommended Solution
The optimal approach configures both custom JWT decoding and resource server security:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder()) // Custom decoder
));
// Additional configurations
.csrf(csrf -> csrf.disable())
.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()));
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json").build();
}
Alternative Configuration Styles
Both of these are functionally equivalent:
http.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
// With bean resolver
@Bean
JwtDecoder jwtDecoder() { /* ... */ }
@Value("${security.jwks-uri}")
private String jwksUri;
http.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwkSetUri(jwksUri))
);
Kotlin DSL Configuration
For Kotlin users, the DSL provides concise configuration:
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/admin/**", hasRole("ADMIN"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt { }
}
headers {
frameOptions { sameOrigin }
}
csrf { disable() }
sessionManagement {
sessionCreationPolicy = SessionCreationPolicy.STATELESS
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder =
NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/jwks.json").build()
Complete Java Example
Combining all configurations in a production-ready setup:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String JWKS_URI = "https://your-auth-domain/.well-known/jwks.json";
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder()))
)
.csrf(csrf -> csrf.disable())
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions.sameOrigin())
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(JWKS_URI).build();
}
}
Key Migration Notes
Best Practice
Test JWKS Connectivity: Always validate your JWKS URI is accessible during startup to prevent runtime authentication failures. Consider adding a health check for this endpoint.
API Changes
and()
is deprecated - Configure everything in a single fluent chain- Method chaining rules changed - Each component must be configured within its own lambda
Customizer.withDefaults()
activates Spring's default configuration for that component
Removed Features
The following are completely removed and have no direct replacements:
antMatchers()
mvcMatchers()
regexMatchers()
OAuth2ResourceServerConfigurer::jwt
method reference
This lambda DSL configuration approach provides more flexibility while reducing common configuration errors. As you migrate older Spring Security setups, consistently apply the configure -> lambda -> component
pattern throughout your security configuration.