In recent versions of Spring Security, the WebSecurityConfigurerAdapter class has been deprecated in favor of a more modular, component-based configuration approach. This article will guide you through the process of migrating your Spring Security configuration from the deprecated WebSecurityConfigurerAdapter to the new component-based approach.

Background on Migrating from WebSecurityConfigurerAdapter

The WebSecurityConfigurerAdapter class has been a core part of Spring Security’s configuration since its early versions. It provided a convenient way to configure various aspects of web security, such as authentication, authorization, and other security-related settings, by overriding its methods.

However, as Spring Security evolved, the team behind the framework recognized the need for a more modular and flexible approach to security configuration. The WebSecurityConfigurerAdapter class, while useful, had some limitations:

  1. Inflexible Ordering: The order in which security configurations were applied was determined by the order of the overridden methods in the adapter class. This could lead to conflicts and unexpected behavior when multiple configurations were involved.
  2. Coupling: By extending the WebSecurityConfigurerAdapter class, developers were tightly coupled to Spring Security’s implementation details, making it harder to migrate to newer versions or integrate with other security frameworks.
  3. Lack of Modularity: The adapter class combined different security concerns (e.g., authentication, authorization, and web security) into a single class, making it more difficult to separate and maintain these concerns independently.

To address these limitations, Spring Security introduced a new component-based configuration approach starting with version 5.7.0-M2. This approach aimed to provide a more modular, flexible, and maintainable way of configuring security in Spring applications.

In the new approach, instead of extending the WebSecurityConfigurerAdapter class, developers create and wire together individual components responsible for different security concerns. These components can be configured using Java configuration classes or functional interfaces, providing a cleaner and more expressive way of defining security rules.

Migration Summary in Brief:

The migration from WebSecurityConfigurerAdapter to the new component-based approach involves the following steps:

  1. Remove the WebSecurityConfigurerAdapter extension: Instead of extending WebSecurityConfigurerAdapter, developers create a regular configuration class annotated with @Configuration and @EnableWebSecurity.
  2. Configure Authentication: Instead of using the AuthenticationManagerBuilder, developers create a UserDetailsService or UserDetailsManager bean to define authentication providers and user details.
  3. Configure HTTP Security: Instead of overriding the configure(HttpSecurity http) method, developers create a SecurityFilterChain bean and configure HTTP security rules within this bean.
  4. Configure Web Security: Instead of overriding the configure(WebSecurity web) method, developers create a WebSecurityCustomizer bean to configure web security settings, such as ignoring certain URL patterns.
  5. Update Tests: If necessary, update any existing tests that relied on the WebSecurityConfigurerAdapter class to use the new component-based approach.

By migrating to the new component-based approach, developers can benefit from a more modular and flexible security configuration, easier maintenance, and better compatibility with future versions of Spring Security. Additionally, the new approach aligns with the broader trend in Spring towards more functional and declarative programming styles, making the code more expressive and easier to reason about.

Upgrade the Dependencies:

XML
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

Changes for WebSecurityConfigurerAdapter Migration

Here are the code examples for each section, showing how it was done in Spring Security 5.0 (the old version) and how it’s done in the newer version (6.2.x).

1. Configuration Class

Spring Security 5.0 (old version):

Java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // Configuration methods...
}

Spring Security 6.2.x (new version):

The @EnableWebSecurity annotation enables web security, and the @EnableMethodSecurity annotation enables method-level security annotations.

Java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
    // Configuration methods...
}

2. Configure Authentication

Spring Security 5.0 (old version):

Java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user").password("{noop}userPass").roles("USER")
        .and()
        .withUser("admin").password("{noop}adminPass").roles("USER", "ADMIN");
}

Spring Security 6.2.x (new version):

Java
@Bean
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("user")
        .password(bCryptPasswordEncoder.encode("userPass"))
        .roles("USER")
        .build());
    manager.createUser(User.withUsername("admin")
        .password(bCryptPasswordEncoder.encode("adminPass"))
        .roles("USER", "ADMIN")
        .build());
    return manager;
}

Configure HTTP Security

    Spring Security 5.0 (old version):

    Java
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers(HttpMethod.DELETE).hasRole("ADMIN")
                .antMatchers("/admin/**").hasAnyRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/login/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .httpBasic()
                .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    Spring Security 6.2.x (new version):

    Java
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                authorizationManagerRequestMatcherRegistry
                    .requestMatchers(HttpMethod.DELETE).hasRole("ADMIN")
                    .requestMatchers("/admin/**").hasAnyRole("ADMIN")
                    .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                    .requestMatchers("/login/**").permitAll()
                    .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults())
            .sessionManagement(httpSecuritySessionManagementConfigurer ->
                httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    
        return http.build();
    }

    Configure Web Security

      Spring Security 5.0 (old version):

      Java
      @Override
      public void configure(WebSecurity web) throws Exception {
          web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
      }

      Spring Security 6.2.x (new version):

      Java
      @Bean
      public WebSecurityCustomizer webSecurityCustomizer() {
          return web -> web.debug(true)
              .ignoring()
              .requestMatchers("/css/**", "/js /**", "/img/**", "/lib/**", "/favicon.ico");
      }

      Testing the Security Configs

      Add the test dependencies:

      XML
      
      <dependencies>  
        
          <!-- Spring Boot Starter Test -->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-test</artifactId>
              <scope>test</scope>
          </dependency>
      
          <!-- Spring Security Test -->
          <dependency>
              <groupId>org.springframework.security</groupId>
              <artifactId>spring-security-test</artifactId>
              <scope>test</scope>
          </dependency>
          
      </dependencies>

      Spring Security 6.2.x (new version):

      Java
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.security.test.context.support.WithAnonymousUser;
      import org.springframework.security.test.context.support.WithMockUser;
      import org.springframework.test.web.servlet.MockMvc;
      
      import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
      import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
      
      @SpringBootTest
      @AutoConfigureMockMvc
      public class SecurityTests {
      
          @Autowired
          private MockMvc mockMvc;
      
          @Test
          @WithAnonymousUser
          public void whenAnonymousAccessLogin_thenOk() throws Exception {
              mockMvc.perform(get("/login"))
                     .andExpect(status().isOk());
          }
      
          @Test
          @WithAnonymousUser
          public void whenAnonymousAccessRestrictedEndpoint_thenUnauthorized() throws Exception {
              mockMvc.perform(get("/user"))
                     .andExpect(status().isUnauthorized());
          }
      
          @Test
          @WithMockUser(username = "user", roles = {"USER"})
          public void whenUserAccessUserEndpoint_thenOk() throws Exception {
              mockMvc.perform(get("/user"))
                     .andExpect(status().isOk());
          }
      
          @Test
          @WithMockUser(username = "user", roles = {"USER"})
          public void whenUserAccessAdminEndpoint_thenForbidden() throws Exception {
              mockMvc.perform(get("/admin"))
                     .andExpect(status().isForbidden());
          }
      
          @Test
          @WithMockUser(username = "admin", roles = {"ADMIN"})
          public void whenAdminAccessUserEndpoint_thenOk() throws Exception {
              mockMvc.perform(get("/user"))
                     .andExpect(status().isOk());
          }
      
          @Test
          @WithMockUser(username = "admin", roles = {"ADMIN"})
          public void whenAdminAccessAdminEndpoint_thenOk() throws Exception {
              mockMvc.perform(get("/admin"))
                     .andExpect(status().isOk());
          }
      
          @Test
          @WithMockUser(username = "admin", roles = {"ADMIN"})
          public void whenAdminAccessDeleteEndpoint_thenOk() throws Exception {
              mockMvc.perform(get("/delete").content("{}"))
                     .andExpect(status().isOk());
          }
      }
      

      As you can see, the testing code remains the same in both versions.

      Explanations:

      1. Configuration Class: In Spring Security 5.0, we extended the WebSecurityConfigurerAdapter class to configure security. In the newer version (6.2.x), we no longer extend this class. Instead, we use annotations like @EnableWebSecurity and @EnableMethodSecurity to enable web and method-level security, respectively.
      2. Configure Authentication: In Spring Security 5.0, we used the configure(AuthenticationManagerBuilder auth) method to configure in-memory authentication. In the newer version, we create a UserDetailsService or UserDetailsManager bean to define our users and their roles. This example uses an in-memory UserDetailsManager.
      3. Configure HTTP Security: In Spring Security 5.0, we overrode the configure(HttpSecurity http) method to configure HTTP security rules. In the newer version, we create a SecurityFilterChain bean and configure the HTTP security rules within this bean.
      4. Configure Web Security: In Spring Security 5.0, we overrode the configure(WebSecurity web) method to configure web security settings, such as ignoring certain URL patterns. In the newer version, we create a WebSecurityCustomizer bean to configure web security settings.
      5. Testing: The testing code remains largely the same in both versions. We use the @WithAnonymousUser and @WithUserDetails annotations to test endpoints with different user roles.

      By following these examples, you can migrate your Spring Security configuration from the deprecated WebSecurityConfigurerAdapter approach to the new component-based approach introduced in Spring Security 6.2.x.

      Conclusion:

      In this article, we learned how to migrate from the deprecated WebSecurityConfigurerAdapter to the new component-based approach in Spring Security. We covered configuring authentication, HTTP security, and web security using the latest Spring Security features. Additionally, we provided examples of testing the configured endpoints using Spring’s testing utilities.

      By following this guide, you can upgrade your Spring Security configuration to the latest best practices, ensuring a more modular and maintainable codebase.

      By |Last Updated: May 26th, 2024|Categories: Spring Security|