Enterprise Java

Migrating an Application from Spring Security 5 to Spring Security 6

Spring Security offers extensive security features for Java applications. With each new release, Spring Security brings enhancements, security fixes, and upgraded functionalities. Transitioning our application from Spring Security 5 to Spring Security 6 enables us to utilize the most recent advancements and uphold a secure application environment. This article aims to guide you through the migration process, highlighting some key changes and providing code examples.

1. Why Migrate to Spring Security 6/Spring Boot 3?

Spring Security 6 and Spring Boot 3 introduce various improvements, bug fixes, and updated dependencies that enhance the security and performance of your application. Migrating to these versions ensures you benefit from:

  • Security Enhancements: Spring Security 6 introduces enhanced security features and updates to protect your application against evolving security threats. This includes improvements in authentication mechanisms, authorization controls, and secure defaults.
  • Compatibility with Latest Spring Boot Versions: Spring Security 6 is designed to work seamlessly with the latest versions of Spring Boot (such as Spring Boot 3), providing better integration and compatibility with other Spring ecosystem components.
  • Support for New Features and APIs: By migrating to Spring Security 6, you gain access to new features, APIs, and capabilities introduced in the latest release. This allows you to leverage modern security practices and patterns in your application development.
  • Performance Improvements: Spring Security 6 may include performance optimizations and efficiency enhancements, resulting in better response times and reduced resource utilization. This is crucial for high-traffic applications.
  • Stay Up-to-Date with Community Standards: Staying current with Spring Security versions ensures that your application adheres to community standards and best practices recommended by the Spring framework maintainers and security experts.

2. Preparing for Migration

Before starting the migration process, ensure you are on the latest version of Spring Security 5.8.x. This version prepares your codebase for the transition. You can upgrade Spring Boot to version 2.7.x if applicable and might need to manually override dependency versions:

    <properties>            
        <spring-security.version>5.8.12</spring-security.version>
    </properties>

2.1 Key Changes in Spring Security 6

  • Configuration with WebFilterChain: Spring Security 6 removes the WebSecurityConfigurerAdapter class. Instead, we will define a SecurityFilterChain bean.
  • Lambda Expressions: Spring Security 6 embraces lambda expressions for a cleaner and more concise configuration style.
  • Authentication Providers: The approach to authentication providers has shifted slightly.

2.1 Upgrade Dependencies

To update the project’s dependencies to include Spring Security 6.x.x and Spring Boot 3.x.x, upgrade the parent version spring-boot-starter-parent of the Spring Boot project from 2.7.15 to 3.x.x and it will use the Spring Security 6.x.x compatible version:

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

For example, updating the above project’s pom.xml spring-boot-starter-parent version from 2.7.15 to 3.2.5 updates the project to use spring-boot-starter-security 3.2.5 which uses Spring Security 6.2.4.

Confirm that the pom.xml file includes the dependencies for spring-boot-starter-web and spring-boot-starter-security compatible with Spring Security 6 and Spring Boot 3:

 
        <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>

Note that the Java version requirement for Spring Boot 3 is a minimum of Java 17.

2.2 Security Configuration Evolution

Review your Spring Security configuration classes (SecurityConfig.java or similar) for any changes required in configuration patterns or security rules introduced in Spring Security 6. Below are examples demonstrating key changes when migrating from Spring Security 5 to Spring Security 6.

2.2.1 Authentication and Configuration Changes

Spring Security 5 Configuration (SecurityConfig.java)

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String ADMIN = "ADMIN";
    private static final String USER = "USER";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api/employees/**").hasRole(USER)
                .antMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin()
                .and()
                .logout()
                .and()
                .csrf()
                .disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("jcg")
                .password("javacodegeeks")
                .roles(ADMIN)
                .username("william")
                .password("lee")
                .roles(USER)
                .build();
        auth.inMemoryAuthentication().withUser(user);
    }

}

Spring Security 6 Configuration (SecurityConfig.java)

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final String ADMIN = "ADMIN";
    private static final String USER = "USER";

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(request -> request
                .requestMatchers("/api/employees/**").hasRole(USER)
                .requestMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated())
                .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable())
                .formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
                .logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.disable())
                .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User
                .withUsername("thomas")
                .password(encoder().encode("paine"))
                .roles(ADMIN).build());
        manager.createUser(User
                .withUsername("bill")
                .password(encoder().encode("withers"))
                .roles(USER).build());
        return manager;
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

}

3. Differences: Spring Security 5 vs. Spring Security 6

Below are some differences between the two Spring Security configurations:

3.1 Configuration Style

In Spring Security 5, a typical security configuration class looks like:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

In Spring Security 6, it is now:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

In the first configuration with Spring Security 5, SecurityConfig extends WebSecurityConfigurerAdapter. This was the traditional approach to configuring Spring Security, whereby a class extends the specific base class of WebSecurityConfigurerAdapter.

In the second configuration with Spring Security 6, SecurityConfig is annotated with @Configuration and @EnableWebSecurity, indicating that it’s a standalone configuration class for Spring Security.

3.2 Configuring HTTP Security

In Spring Security 5, the configure(HttpSecurity http) method is overridden within a class that extends WebSecurityConfigurerAdapter. This method allows us to configure HTTP security settings, such as URL-based authorization rules, form login, HTTP basic authentication, logout, CSRF protection, and more. The configuration is imperative and directly modifies the provided HttpSecurity object.

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String ADMIN = "ADMIN";
    private static final String USER = "USER";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api/employees/**").hasRole(USER)
                .antMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin()
                .and()
                .logout()
                .and()
                .csrf()
                .disable();
    }

}

In Spring Security 6, the approach shifts towards functional configuration. Instead of overriding a method within WebSecurityConfigurerAdapter, we define a SecurityFilterChain bean using a method annotated with @Bean. This bean defines the security filter chain, including HTTP security configurations, using method chaining or lambda expressions. The HttpSecurity object is passed as a parameter to this method, allowing for configuration within the bean definition.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final String ADMIN = "ADMIN";
    private static final String USER = "USER";

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(request -> request
                .requestMatchers("/api/employees/**").hasRole(USER)
                .requestMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated())
                .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable())
                .formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
                .logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.disable())
                .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }
}

3.3 Method Configuration vs. Functional Configuration

First Configuration (Spring Security 5)

@Override
protected void configure(HttpSecurity http) throws Exception {

Second Configuration: (Spring Security 6)

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  • In the first configuration, method configuration is used, where you override the configure(HttpSecurity http) method to define security configurations.
  • In the second configuration, functional configuration is used with Java Config DSL, where you define security configurations using method chaining within a SecurityFilterChain bean definition.

3.4 Authentication Configuration

In Spring Security 5, you override the configure(AuthenticationManagerBuilder auth) method that extends the WebSecurityConfigurerAdapter class to define in-memory user details for authentication.

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("jcg")
                .password("javacodegeeks")
                .roles(ADMIN)
                .username("william")
                .password("lee")
                .roles(USER)
                .build();
        auth.inMemoryAuthentication().withUser(user);
    }

With Spring Security 6, we define a method userDetailsService() that returns a UserDetailsService bean to create an InMemoryUserDetailsManager instance to manage user details in memory. This approach is more modular and flexible, allowing for easier management of user details and customization.

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User
                .withUsername("thomas")
                .password(encoder().encode("paine"))
                .roles(ADMIN).build());
        manager.createUser(User
                .withUsername("bill")
                .password(encoder().encode("withers"))
                .roles(USER).build());
        return manager;
    }

3.5 Lambda expressions

In Spring Security 5, we use the .and() definitions between configuration elements and utilized the .antMatchers method of the authorizeRequest object.

        http
                .authorizeRequests()
                .antMatchers("/api/employees/**").hasRole(USER)
                .antMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin()
                .and()
                .logout()
                .and()
                .csrf()
                .disable();

In Spring Security 6, the .and() definitions used between configuration elements in the older configuration (Spring Security 5) have been removed. They have been substituted with lambda expressions in Spring Security 6.2.4. Also, the .antMatchers method has been renamed to .requestMatchers and we replace authorizeRequests() method with authorizeHttpRequests().

   
        http
                .authorizeHttpRequests(request -> request
                .requestMatchers("/api/employees/**").hasRole(USER)
                .requestMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated())
                .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable())
                .formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
                .logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.disable())
                .csrf(AbstractHttpConfigurer::disable);

        

4. Use OpenRewrite for Spring Security 6 Migration

OpenRewrite can automate some of the migration tasks for us. OpenRewrite is a tool that helps automate source code migrations, including upgrading dependencies like Spring Security. Here is how we can utilize OpenRewrite to streamline the migration process from Spring Security 5.0 to 6.0.

4.1 Configuring OpenRewrite in pom.xml

Add the OpenRewrite Maven plugin to the project’s pom.xml file. Below is an example configuration:

     <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            
            <plugin>
                <groupId>org.openrewrite.maven</groupId>
                <artifactId>rewrite-maven-plugin</artifactId>
                <version>5.29.0</version>
                <configuration>
                    <activeRecipes>
                        <recipe>org.openrewrite.java.migrate.UpgradeToJava17</recipe>
                        <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2</recipe>
                    </activeRecipes>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.openrewrite.recipe</groupId>
                        <artifactId>rewrite-migrate-java</artifactId>
                        <version>2.7.1</version>
                    </dependency>
                    <dependency>
                        <groupId>org.openrewrite.recipe</groupId>
                        <artifactId>rewrite-spring</artifactId>
                        <version>5.8.0</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

4.2 Apply Rewrite to Project

Execute the mvn rewrite:run in your terminal to run the migration command. Fig 1 shows the output result of Maven:

Fig 1. Output of using open-rewrite to migrate spring security from 5 to 6
Fig 1. Output of using open-rewrite to migrate spring security from 5 to 6

The full output looks like this:

[WARNING] Changes have been made to Downloads/employee-service-2/pom.xml by:
[WARNING]     org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]         org.openrewrite.java.migrate.JavaVersion17
[WARNING]             org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]         org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2
[WARNING]             org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1
[WARNING]                 org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING]                     org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7
[WARNING]                         org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.7.x}
[WARNING]                     org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.0.x}
[WARNING]                 org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.1.x}
[WARNING]             org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.2.x}
[WARNING] Changes have been made to Downloads/employee-service-2/src/main/java/com/javacodegeeks/example/employeeservice/SecurityConfig.java by:
[WARNING]     org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]         org.openrewrite.java.migrate.JavaVersion17
[WARNING]             org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]         org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2
[WARNING]             org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1
[WARNING]                 org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING]                     org.openrewrite.java.spring.boot3.ConfigurationOverEnableSecurity: {forceAddConfiguration=false}
[WARNING]                     org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_0
[WARNING]                         org.openrewrite.java.spring.security5.UpgradeSpringSecurity_5_8
[WARNING]                             org.openrewrite.java.spring.security5.UpgradeSpringSecurity_5_7
[WARNING]                                 org.openrewrite.java.spring.security5.WebSecurityConfigurerAdapter
[WARNING]                             org.openrewrite.java.spring.security5.AuthorizeHttpRequests
[WARNING]                             org.openrewrite.java.spring.security5.UseNewRequestMatchers
[WARNING]                             org.openrewrite.java.spring.security5.ReplaceGlobalMethodSecurityWithMethodSecurity
[WARNING]                 org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_1
[WARNING]                     org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_0
[WARNING]                         org.openrewrite.java.spring.security5.UpgradeSpringSecurity_5_8
[WARNING]                             org.openrewrite.java.spring.security5.UseNewRequestMatchers
[WARNING]                     org.openrewrite.java.spring.boot2.HttpSecurityLambdaDsl
[WARNING]             org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_2
[WARNING]                 org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_1
[WARNING]                     org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_0
[WARNING]                         org.openrewrite.java.spring.security5.UpgradeSpringSecurity_5_8
[WARNING]                             org.openrewrite.java.spring.security5.UseNewRequestMatchers
[WARNING]                     org.openrewrite.java.spring.boot2.HttpSecurityLambdaDsl
[WARNING] Please review and commit the results.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Next, analyze and observe the difference and update your codebase based on the configured recipes.

5. Conclusion

In this article, we explored the process of migrating from Spring Security 5.0 to 6.0, emphasizing the importance of staying updated with the latest security enhancements and features. We discussed the manual migration steps, such as updating dependencies, configuring security settings, and handling deprecated APIs. Additionally, we highlighted the use of OpenRewrite as a tool for automating code migrations, ensuring a smoother transition to the newer version of Spring Security.

Both configurations achieve the same goal of configuring Spring Security for the application but they use different approaches and APIs. The first configuration follows the traditional method-based approach, while the second configuration utilizes a newer functional configuration.

6. Download the Source Code

This was an article on how to migrate from Spring Security 5 to 6.

Download
You can download the full source code of this example here: Spring Security: Migrate from 5 to 6

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button