Skip to content
Ashish.
All posts
Diagram showing migration from WebSecurityConfigurerAdapter to SecurityFilterChain beans.

Migrating from WebSecurityConfigurerAdapter to Spring Security 6

A practical guide for Java developers migrating from WebSecurityConfigurerAdapter to Spring Security 6 and Spring Boot 3 using authorizeHttpRequests.

By Ashish KumarPart 8 of Spring Security Filter Chain Mastery

Migrating From WebSecurityConfigurerAdapter

For years, WebSecurityConfigurerAdapter served as the backbone of Spring Security configuration. Developers extended this class and overrode its configure methods to dictate how HTTP requests were secured. However, with the release of Spring Security 6 and Spring Boot 3, this approach has been removed in Spring Security 6.0. This is not merely a deprecation warning; it is a breaking change that forces a fundamental rethink of how security rules are composed.

This guide walks you through the structural shift from configuration inheritance to explicit bean composition, ensuring your application remains secure and maintainable.

Why the Adapter Was Removed

The WebSecurityConfigurerAdapter pattern relied on Java inheritance to inject configuration logic. While convenient for simple applications, it created significant problems as complexity grew:

  1. Single Configuration Constraint: Although multiple adapters could be defined using the @Order annotation to separate rules for /api/** and /admin/**, doing so required verbose boilerplate and careful ordering, often leading to fragile configurations.
  2. Implicit Defaults: The adapter hid many default behaviors. Developers often didn't know what was happening under the hood because the adapter applied sensible defaults automatically.
  3. Testing Complexity: Mocking or testing individual security configurations was difficult because they were tightly coupled to the adapter's lifecycle.

Spring Security 6 addresses these issues by treating security configuration as a set of discrete, composable beans. The new model is explicit: if you want a security rule, you define a SecurityFilterChain bean that contains it. This shift is a core component of the spring security 6 migration strategy, moving away from fragile inheritance hierarchies toward a more robust, functional programming model. For detailed architectural reasoning, refer to the official Spring Security reference documentation on configuration changes.

In practice, this migration often surfaces hidden issues. For instance, during a recent migration of a high-throughput e-commerce platform, we encountered a NoSuchBeanDefinitionException when removing the adapter because an older custom filter relied on implicit inheritance from the parent HttpSecurity builder.

The New Pattern: SecurityFilterChain Beans

In Spring Boot 3 with Spring Security 6, the primary artifact for security configuration is the SecurityFilterChain bean. You define this bean in a @Configuration class, and Spring Boot automatically detects it and integrates it into the application’s filter chain.

Here is a practical migration example.

Old Code (Spring Security 5 / Boot 2)

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .formLogin();
    }
}

New Code (Spring Security 6 / Boot 3)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form.permitAll());
        
        return http.build();
    }
}

Mechanism Breakdown

  1. Bean Definition: The @Bean annotation on securityFilterChain signals to Spring that this method produces a component for the security filter chain. The framework collects all such beans and merges them into a single FilterChainProxy.
  2. authorizeHttpRequests: This method replaces authorizeRequests(). It takes a lambda or DSL builder to define authorization rules. The authorizehttprequests DSL provides a type-safe way to define access constraints without the verbosity of previous versions.
  3. requestMatchers: This replaces antMatchers() and regexMatchers(). It provides a more consistent and type-safe way to match HTTP requests. Under the hood, requestMatchers uses Spring’s RequestMatcher interface, allowing for more flexible matching strategies (e.g., combining path patterns with HTTP methods).
  4. No .and(): The old adapter required .and() to chain back to the parent HttpSecurity builder. In the new model, the DSL is fluent within the lambda, so no .and() is needed. You simply configure the next part of the chain directly.
  5. Explicit Build: The http.build() call is crucial. It finalizes the configuration and returns the SecurityFilterChain instance. Without this, the bean would not be created.

Key Behavioral Shifts

During migration, you will encounter several specific API changes that require attention.

1. antMatchers vs. requestMatchers

The antMatchers method is deprecated and removed. Use requestMatchers instead. It supports the same Ant-style path patterns but also allows for more complex matchers.

// Old
.antMatchers("GET", "/api/**").hasRole("USER")
 
// New
.requestMatchers("GET", "/api/**").hasRole("USER")

You can also combine matchers for more precise control. In the new DSL, you typically chain them directly within the authorizeHttpRequests lambda:

.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/api/**").hasRole("USER")
    .anyRequest().authenticated()
)

Note that .and() is not used in the authorizeHttpRequests lambda DSL for chaining request matchers; they are chained directly. The .and() operator is reserved for moving back to the parent HttpSecurity builder to configure other aspects like form login or CSRF, not for adding more authorization rules.

2. CSRF Protection

In Spring Security 5, CSRF protection was enabled by default. In Spring Security 6, CSRF protection is still enabled by default, but the configuration is more explicit. If you are using stateless APIs (e.g., JWT), you must explicitly disable CSRF.

.csrf(csrf -> csrf.disable())

3. Session Management

Session management options are now configured via the sessionManagement builder. For example, to prevent concurrent sessions:

.sessionManagement(session -> session
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)
)

4. Authentication Providers

Authentication providers (like DaoAuthenticationProvider) are no longer configured via configure(AuthenticationManagerBuilder). Instead, you define them as beans and inject them into the AuthenticationManager bean.

For most standard setups, Spring Boot’s auto-configuration handles the AuthenticationManager creation automatically. You typically only need to define a custom AuthenticationManager bean if you have specific requirements that deviate from the defaults, such as integrating with a custom UserDetailsService or using a non-standard PasswordEncoder. If customization is required, you can use AuthenticationConfiguration to obtain the AuthenticationManager bean or define your own AuthenticationManagerBuilder bean, which Spring will then use to construct the manager.

@Bean
public AuthenticationManager authenticationManager(
        AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

This approach leverages the existing infrastructure while allowing you to override specific behaviors if necessary.

Conclusion

Migrating from WebSecurityConfigurerAdapter is a necessary step to leverage the full capabilities of Spring Security 6 and Spring Boot 3. The new model is more verbose but significantly more powerful and predictable. By understanding the mechanism of SecurityFilterChain beans and authorizeHttpRequests, you can create secure, maintainable, and testable configurations that scale with your application’s complexity.

Start by replacing your WebSecurityConfigurerAdapter with a SecurityFilterChain bean, update all antMatchers to requestMatchers, and review your CSRF and session management settings. The learning curve is steep, but the resulting clarity is worth the effort.

Audit your current security configuration today. Identify all instances of WebSecurityConfigurerAdapter and begin the transition to the new bean-based model to ensure your application remains secure and compatible with the latest Spring Boot releases.

Related posts