Skip to content

Spring Security 6 JWT Configuration

Problem Statement

When upgrading to Spring Security 6, developers face two significant deprecations:

  1. JWT Configuration: The direct OAuth2ResourceServerConfigurer::jwt method reference is deprecated and no longer functional
  2. Endpoint Matchers: Traditional security matchers like antMatchers(), mvcMatchers(), and regexMatchers() 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:

  1. Type-Safe Configuration: Prevent common misconfiguration errors
  2. Consistent DSL: Unified approach for all security configurations
  3. Modern Java Practices: Leverage functional interfaces and lambdas

Endpoint Security Configuration

Replacing antMatchers

Traditional matchers are replaced by the versatile requestMatchers() method:

java
http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/public/**").permitAll()   // New replacement
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .anyRequest().authenticated()
);

JWT Resource Server Configuration

The optimal approach configures both custom JWT decoding and resource server security:

java
@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:

java
http.oauth2ResourceServer(oauth2 -> oauth2
    .jwt(Customizer.withDefaults())
);

// With bean resolver
@Bean
JwtDecoder jwtDecoder() { /* ... */ }
java
@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:

kotlin
@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:

java
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

  1. and() is deprecated - Configure everything in a single fluent chain
  2. Method chaining rules changed - Each component must be configured within its own lambda
  3. 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.