Back to Blog
The Component Revolution: Mastering Spring Security 6.x Bean Migration
Security

The Component Revolution: Mastering Spring Security 6.x Bean Migration

The definitive guide to migrating from WebSecurityConfigurerAdapter to the SecurityFilterChain bean model. Understand the "why", master the "how", and future-proof your security configuration.

February 2, 20264 min readRabi
Spring BootJavaSecurityMigrationArchitecture

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.

The Why: Inheritance vs. 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.

The Modern Way: SecurityFilterChain

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:

  1. Disable CSRF (because who needs it for APIs, right?).
  2. Allow public access to /auth/*.
  3. Lock down everything else.
  4. 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();
    }
}
Spring Security Filter Chain Diagram

Notice the shift to DSL Lambdas (csrf -> csrf.disable()). This makes the nesting and configuration scope explicit, improving readability and reducing indentation hell.

Advanced: Multiple Filter Chains

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.

Frequently Asked Questions

Why was WebSecurityConfigurerAdapter deprecated?

It encouraged a rigid, inheritance-based configuration that made it difficult to compose multiple security policies. The new component-based model allows for more flexible, independent SecurityFilterChain beans.

Can I have multiple SecurityFilterChain beans?

Yes! This is the power of the new model. You can define multiple beans with different `@Order` annotations to handle API routes, Admin panels, and public UI logic separately.

How do I expose the AuthenticationManager now?

Instead of overriding `authenticationManagerBean()`, you simply inject `AuthenticationConfiguration` and call `configuration.getAuthenticationManager()`.

Join the Community

Get the latest articles on system design, frontend & backend development, and emerging tech trends delivered straight to your inbox.

No spam. Unsubscribe at any time.

Liked the blog?

Share it with your friends and help them learn something new!