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:
- 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.
- 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. - 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:
- Remove the
WebSecurityConfigurerAdapter
extension: Instead of extendingWebSecurityConfigurerAdapter
, developers create a regular configuration class annotated with@Configuration
and@EnableWebSecurity
. - Configure Authentication: Instead of using the
AuthenticationManagerBuilder
, developers create aUserDetailsService
orUserDetailsManager
bean to define authentication providers and user details. - Configure HTTP Security: Instead of overriding the
configure(HttpSecurity http)
method, developers create aSecurityFilterChain
bean and configure HTTP security rules within this bean. - Configure Web Security: Instead of overriding the
configure(WebSecurity web)
method, developers create aWebSecurityCustomizer
bean to configure web security settings, such as ignoring certain URL patterns. - 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:
<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):
@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.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
// Configuration methods...
}
2. Configure Authentication
Spring Security 5.0 (old version):
@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):
@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):
@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):
@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):
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}
Spring Security 6.2.x (new version):
@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:
<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):
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:
- 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. - 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 aUserDetailsService
orUserDetailsManager
bean to define our users and their roles. This example uses an in-memoryUserDetailsManager
. - 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 aSecurityFilterChain
bean and configure the HTTP security rules within this bean. - 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 aWebSecurityCustomizer
bean to configure web security settings. - 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.