从 Spring Security 5 迁移到 Spring Security 6/Spring Boot 3

1、概览

Spring Security 6 有几处重大变化,包括删除了一些类和已废弃的方法,并引入了一些新方法。

从 Spring Security 5 迁移到 Spring Security 6 可以在不破坏现有代码的情况下逐步完成。此外,还可以使用 OpenRewrite 等第三方插件来促进向最新版本的迁移。

本文将带你了解如何把 Spring Security 5 的现有应用迁移到 Spring Security 6(替换过时的方法,并利用 Lambda DSL 简化配置。此外,还利用 OpenRewrite 加快迁移速度)。

2、Spring Security 和 Spring Boot 版本

Spring Boot 基于 Spring 框架,各版本的 Spring Boot 使用最新版本的 Spring 框架。Spring Boot 2 默认使用 Spring Security 5,而 Spring Boot 3 使用 Spring Security 6。

要在 Spring Boot 应用中使用 Spring Security,首先需要在 pom.xml 中添加 spring-boot-starter-security 依赖。

可以在 pom.xmlproperties 部分 指定所需的版本,从而覆盖默认的 Spring Security 版本 :

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

如上,指定在项目中使用 Spring Security 5.8.9,覆盖默认版本。通过这种方法,可以在 Spring Boot 2 中使用 Spring Security 6。

3、Spring Security 6 的新功能

Spring Security 6 引入了多项功能更新,以提高安全性和健壮性。它现在至少需要 Java 17,并使用 jakarta 命名空间。

主要变化之一是删除了 WebSecurityConfigurerAdapter,转而使用基于组件的安全配置。

此外,还删除了 authorizeRequests() 并代之以 authorizeHttpRequests() 来定义授权规则(Authorization Rule)。

它还引入了 requestMatcher()securityMatcher() 等方法 , 以取代 antMatcher()mvcMatcher() 来配置请求资源的安全配置。requestMatcher() 方法更安全,因为它会为请求配置选择合适的 RequestMatcher 实现。

其他已废弃的方法,如 cors()csrf(),现在有了函数式的替代方法。

4、项目设置

首先,在 pom.xml 中添加 spring-boot-starter-webspring-boot-starter-security 来创建 Spring Boot 2.7.5 项目 :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.5</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.5</version>
</dependency>

spring-boot-starter-security 依赖使用 Spring Security 5。

接下来,创建一个 WebSecurityConfig 配置类:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

如上,该类继承了 WebSecurityConfigurerAdapter 类,继承了各种安全配置方法。用 @EnableWebSecurity 对该类进行注解,以启动为 Web 请求配置安全配置的过程。此外,还启用了方法级授权。

此外,定义一个基于内存的用户,用于身份认证:

@Override
void configure(AuthenticationManagerBuilder auth) throws Exception {
    UserDetails user = User.withDefaultPasswordEncoder()
      .username("Admin")
      .password("password")
      .roles("ADMIN")
      .build();
    auth.inMemoryAuthentication().withUser(user);
}

如上,通过覆写 configure 方法创建了一个内存用户。

接下来,覆写 configure(WebSecurity web) 方法,将静态资源排除在安全配置之外:

@Override
void configure(WebSecurity web) {
    web.ignoring().antMatchers("/js/**", "/css/**");
}

最后,通过覆写 configure(HttpSecurity http) 方法来创建 HttpSecurity

@Override
void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .antMatchers("/").permitAll()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .and()
      .httpBasic();
}

该设置展示了典型的 Spring Security 5 配置。接下来,我们要把这些代码移植到 Spring Security 6。

5、将项目迁移到 Spring Security 6

Spring 建议采用增量迁移方法,以防止在升级到 Spring Security 6 时破坏现有代码。在升级到 Spring Security 6 之前,可以先将 Spring Boot 应用升级到 Spring Security 5.8.5,并更新代码以使用新功能。迁移到 5.8.5 版本为预期的版本 6 变更做好准备。

在增量迁移过程中,IDEA 会提醒我们注意已废弃的功能。这有助于增量更新过程。

为了简化起见,我们通过将应用更新为 Spring Boot 3.2.2 来直接将示例项目迁移到 Spring Security 6。如果应用使用的是 Spring Boot 2,可以在 properties 部分中指定 Spring Security 6。

修改 pom.xml 以使用最新的 Spring Boot 版本:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.2</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.2.2</version>
</dependency>

在初始的设置中,使用的是 Spring Boot 2.7.5,它默认使用 Spring Security 5。

注意,Spring Boot 3 的最低 Java 版本是 Java 17

接下来,重构现有代码以使用 Spring Security 6。

5.1、@Configuration 注解

在 Spring Security 6 之前,@Configuration 注解是 @EnableWebSecurity 的一部分(元注解),但在最新更新后,必须用 @Configuration 来注解我们的安全配置:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

如上,单独引入了 @Configuration,因为它不再是 @EnableWebSecurity 注解的 一部分。也不再是 @EnableMethodSecurity@EnableWebFluxSecurity@EnableGlobalMethodSecurity 注解的一部分。

此外,@EnableGlobalMethodSecurity 被标记为已弃用,并将被 @EnableMethodSecurity 替代。默认情况下,它启用了 Spring 的 pre-post 注解。因此,我们引入 @EnableMethodSecurity 来在方法级别提供授权功能。

5.2、WebSecurityConfigurerAdapter

最新更新删除了 WebSecurityConfigurerAdapter 类,采用了基于组件的配置:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
}

在这里,我们移除了 WebSecurityConfigurerAdapter,这样就不需要再覆写安全配置的方法了。相反,我们可以注册 Bean 来进行安全配置。我们可以注册WebSecurityCustomizer Bean 来配置 Web Security,SecurityFilterChain Bean 来配置 HTTP Security,InMemoryUserDetails Bean 来注册自定义用户等等。

5.3、WebSecurityCustomizer Bean

通过定义 WebSecurityCustomizer Bean 来排除静态资源:

@Bean
WebSecurityCustomizer webSecurityCustomizer() {
   return (web) -> web.ignoring().requestMatchers("/js/**", "/css/**");
}

WebSecurityCustomizer 接口取代了 WebSecurityConfigurerAdapter 接口中的configure(Websecurity web) 方法。

5.4、AuthenticationManager Bean

在前面的章节中,我们通过覆写 WebSecurityConfigurerAdapter 中的 configure(AuthenticationManagerBuilder auth) 方法创建了一个内存用户。

现在,通过注册 InMemoryUserDetailsManager Bean 来重构认证凭证:

@Bean
InMemoryUserDetailsManager userDetailsService() {
    UserDetails user = User.withDefaultPasswordEncoder()
      .username("Admin")
      .password("admin")
      .roles("USER")
      .build();

    return new InMemoryUserDetailsManager(user);
}

如上,定义了一个具有 USER 角色的内存用户,以提供基于角色的授权。

5.5、HTTP Security 配置

在以前的 Spring Security 版本中,我们通过覆写 WebSecurityConfigurer 类中的 configure 方法来配置 HttpSecurity。由于在最新版本中删除了该方法,所以需要通过注册 SecurityFilterChain Bean 来配置 HTTP Security:

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(
          request -> request
            .requestMatchers("/").permitAll()
            .anyRequest().authenticated()
      )
      .formLogin(Customizer.withDefaults())
      .httpBasic(Customizer.withDefaults());
   return http.build();
}

在上面的代码中,我们用 authorizeHttpRequests() 方法替换了authorizeRequest() 方法。新方法使用 AuthorizationManager API,简化了重用和定制。

此外,它还能通过延迟身份认证查询来提高性能。只有当请求需要授权时,才会进行身份认证查询。

如果没有自定义规则,就使用 Customizer.withDefaults() 方法来使用默认配置。

此外,使用 requestMatchers() 而不是 antMatcher()mvcMatcher() 来确保资源安全。

5.6、RequestCache

请求缓存有助于在需要进行身份认证时保存用户请求,并在用户成功通过身份认证后将其重定向到请求。在 Spring Security 6 之前,RequestCache 会在每个传入请求上进行检查,以查看是否有保存的请求需要重定向。这会读取每个 RequestCache 上的 HttpSession

在 Spring Security 6 中,请求缓存只检查请求是否包含名为 “continue” 的特殊参数。这不仅提高了性能,还避免了不必要地读取 HttpSession

6、使用 OpenRewrite

此外,还可以使用 OpenRewrite 等第三方工具将现有的 Spring Boot 应用迁移到 Spring Boot 3。由于 Spring Boot 3 使用 Spring Security 6,因此它也会将 Security 配置迁移到版本 6。

要使用 OpenRewrite,可以在 pom.xml 中添加一个插件:

<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>5.23.1</version>
    <configuration>
        <activeRecipes>
            <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0</recipe>
        </activeRecipes>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.openrewrite.recipe</groupId>
            <artifactId>rewrite-spring</artifactId>
            <version>5.5.0</version>
        </dependency>
    </dependencies>
</plugin>

如上,通过 recipe 属性指定升级到 Spring Boot 3。OpenRewrite 有很多用于升级 Java 项目的 recipe 可供选择。

最后,运行迁移命令:

$ mvn rewrite:run

上述命令将项目迁移到 Spring Boot 3,包括安全配置。不过,OpenRewrite 目前没有为迁移的安全配置使用 lambda DSL。当然,这可能会在未来的版本中有所改变。

7、总结

本文介绍了如何通过替换已废弃的类和方法,将使用 Spring Security 5 的现有代码迁移到 Spring Security 6。此外,还了解了如何使用第三方插件自动完成迁移。


Ref:https://www.baeldung.com/spring-security-migrate-5-to-6