The landscape of Java security has completely shifted. If you've been working with Spring Boot for more than a few years, your muscle memory probably starts every security config with extends WebSecurityConfigurerAdapter.
I know mine did. For years, that class was my safety blanket.
But in Spring Security 5.7, it was deprecated. And in Spring Security 6? It's gone.
Now, before you panic (or get annoyed at yet another breaking change), let me tell you: This is actually a good thing. It’s not just a rename; it’s a total paradigm shift from Inheritance to Composition.
In this guide, we’re going to dismantle the old ways and rebuild a modern, component-based security architecture together.
Why did Spring Security move from inheritance to composition?
Let's be real—the legacy configuration was a bit of a monolith. You had one class that controlled everything.
// LEGACY: The Monolith
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// ... mixed concerns ...
}
}The problem? Well, what if you wanted distinct security rules for your /api/v1/* endpoints (Stateless, JWT) and your /admin/* dashboard (Stateful, Session)? You had to create multiple static classes, deal with complex ordering, and fight the framework. It was a mess.
How do you configure SecurityFilterChain in Spring Security 6.x?
In this new "Component Revolution," HttpSecurity is just a builder that produces a SecurityFilterChain. You register this chain as a bean. The IoC container handles the rest.
It feels much more like "Spring" than the old way ever did.
Side-by-Side Comparison
Let's look at a standard configuration. We want to:
- Disable CSRF (because who needs it for APIs, right?).
- Allow public access to
/auth/*. - Lock down everything else.
- Go stateless with our sessions.
// LEGACY (Spring Boot 2.x)
@Configuration
public class LegacySecurityConfig
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http)
throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
.and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
}
}// MODERN (Spring Boot 3.x / Security 6)
@Configuration
@EnableWebSecurity
public class ModernSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
Notice the shift to DSL Lambdas (csrf -> csrf.disable()). This makes the nesting and configuration scope explicit, improving readability and reducing indentation hell.
How do you use multiple SecurityFilterChains for different endpoints?
The true power comes when you need to mix strategies.
Imagine you have a specialized chain just for your internal actuator endpoints. You want this to strictly check for a specific Role and have a higher precedence.
@Bean
@Order(1) // Runs BEFORE the main chain
public SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http
.securityMatcher("/actuator/**") // Only matches these URLs
.authorizeHttpRequests(auth -> auth
.anyRequest().hasRole("ADMIN")
)
.httpBasic(withDefaults()); // Simple auth for monitoring tools
return http.build();
}
@Bean
@Order(2) // Fallback chain
public SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// ... standard app rules ...
)
// ... JWT filters ...
return http.build();
}Custom Request Matchers
In the legacy version, creating custom matching logic was verbose. Now, RequestMatcher is a functional interface.
@Bean
public SecurityFilterChain customChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(request ->
request.getHeader("X-API-KEY") != null
).permitAll()
.anyRequest().authenticated()
);
return http.build();
}Conclusion
The "Component Revolution" in Spring Security forces us to stop thinking about "Configuring the Framework" and start thinking about "Defining Security Beans".
It aligns Spring Security with the rest of the Spring ecosystem: explicit, composable, and easier to test.
Upgrading to Spring Boot 3? Don't just Ctrl+C, Ctrl+V your old config. Embrace the chain.
For the official migration guide, see the Spring Security 6.x Migration Documentation and the Spring Security Architecture Reference.
Keep Reading
- Full-Stack Social Identity: Spring Security 6.x + Next.js 16 + Auth.js — Apply the new SecurityFilterChain model to a real OAuth2 Resource Server.
- Zero Trust Architecture in Spring Boot Microservices — Take security further with mTLS and custom AuthorizationManagers.
- AI-Driven Anomaly Detection with Spring Security — Combine Spring Security events with AI-powered threat detection.
