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,我們始終將 spring-boot-starter-security 依賴添加到 pom.xml 中。
但是,我們可以通過在 properties 部分中指定所需的版本來覆蓋默認的 Spring Security 版本:
<properties>
<spring-security.version>5.8.9</spring-security.version>
</properties>
在這裏,我們指定我們在項目中使用了 Spring Security 5.8.9,從而覆蓋了默認版本。
值得注意的是,我們也可以在 Spring Boot 2 中使用 Spring Security 6,方法是覆蓋默認版本,具體方法是在 properties 部分中。
3. Spring Security 6 中的新功能
Spring Security 6 引入了多個功能更新,以提高安全性及健壯性。它現在需要 Java 版本 17 或更高版本,並使用 jakarta 命名空間。
主要變更之一是棄用 WebSecurityConfigurerAdapter,改為基於組件的安全性配置。
此外,authorizeRequests() 已被移除並替換為 authorizeHttpRequests() 以定義授權規則。
更重要的是,它引入了 requestMatcher() 和 securityMatcher() 方法來替換 antMatcher() 和 mvcMatcher(),用於配置針對請求資源的安全性。 requestMatcher() 方法更安全,因為它會為請求配置選擇合適的 RequestMatcher 實現。
其他已棄用的方法,如 cors() 和 csrf(),現在具有功能式替代方案。
4. 項目設置
為了開始,讓我們通過添加 spring-boot-starter-web 和 spring-boot-starter-security 到 pom.xml:
<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 {
}
在這裏,我們用 @EnableWebSecurity 註解來初始化對 Web 請求進行安全配置的過程。 此外,我們啓用了方法級別的授權。 接下來,該類繼承了 WebSecurityConfigurerAdapter 類,以提供各種安全配置方法。
此外,讓我們定義一個用於身份驗證的內存用户:
@Override
void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetails user = User.withDefaultPasswordEncoder()
.username("Admin")
.password("password")
.roles("ADMIN")
.build();
auth.inMemoryAuthentication().withUser(user);
}
在上述方法中,我們通過覆蓋默認配置創建了一個內存用户。
接下來,讓我們通過覆蓋 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. Migrating Project to Spring Security 6
Spring 推薦採用分階段遷移的方法,以防止更新到 Spring Security 6 時破壞現有代碼。 在升級到 Spring Security 6 之前,我們可以首先將我們的 Spring Boot 應用程序升級到 Spring Security 5.8.5,並更新代碼以使用新功能。
遷移到 5.8.5 有助於為版本 6 預期的變化做準備。
在分階段遷移過程中,我們的 IDE 可以提醒我們關於已棄用的功能。 這有助於分階段更新過程。
為了簡化遷移過程,讓我們直接將樣本項目遷移到 Spring Security 6,通過更新應用程序以使用 Spring Boot 版本 3.3.2。
在應用程序使用 Spring Boot 版本 2 的情況下,我們可以將 Spring Security 6 指定到 properties 部分。
為了開始遷移過程,讓我們修改 pom.xml 以使用最新版本的 Spring Boot:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.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 安全性,SecurityFilterChain bean 來配置 HTTP 安全性,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
在較早的章節中,我們通過覆蓋 configure(AuthenticationManagerBuilder auth) 方法來自 WebSecurityConfigurerAdapter 創建了一個內存用户。
讓我們通過註冊 InMemoryUserDetailsManager bean 來重構身份驗證憑據邏輯:
@Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("Admin")
.password("admin")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
在這裏,我們定義了一個內存用户,具有 USER 角色,以提供基於角色的授權。
5.5. HTTP 安全配置
在 Spring Security 之前的版本中,我們通過覆蓋 WebSecurityConfigurer 類中的 configure 方法來配置 HttpSecurity。 由於最新版本中已刪除該方法,因此我們應該註冊 SecurityFilterChain bean 來進行 HTTP 安全配置:
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(
request -> request
.requestMatchers("/").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
在上面的代碼中,我們用 authorizeRequest() 方法替換了 authorizeHttpRequests() 方法。 新方法使用 AuthorizationManager API,這簡化了重用和自定義。
此外,它通過延遲身份驗證查找來提高性能。 身份驗證查找僅在請求需要授權時發生。
當沒有自定義規則時,我們使用 Customizer.withDefaults() 方法來使用默認配置。
此外,我們使用 requestMatchers() 而不是 antMatcher() 或 mvcMatcher() 來安全資源。
5.6. RequestCache
請求緩存有助於保存用户請求,當需要身份驗證和將用户重定向到請求時,請求緩存保存用户請求。 在 Spring Security 6 之前,RequestCache 會在每個請求上檢查,以查看是否有任何保存的請求,以便將其重定向到該請求。 這會讀取 HttpSession 在每個 RequestCache 上。
然而,在 Spring Security 6 中,請求緩存僅檢查請求是否包含名為 "continue" 的特殊參數。 這提高了性能,並防止了不必要的讀取 HttpSession。
6. 使用 OpenRewrite
此外,我們還可以使用第三方工具,如 OpenRewrite,將現有的 Spring Boot 應用程序遷移到 Spring Boot 3。由於 Spring Boot 3 使用 Spring Security 6,它也同時將安全配置遷移到版本 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 項目的菜譜。
最後,讓我們運行遷移命令:
$ mvn rewrite:run
上述命令將項目遷移到 Spring Boot 3,包括安全配置。但是,OpenRewrite 目前尚不使用 lambda DSL 進行遷移後的安全配置。當然,這很可能在未來的版本中發生變化。
7. 結論
在本文中,我們學習瞭如何使用 Spring Security 5 遷移現有代碼庫到 Spring Security 6 的逐步指南,通過替換已棄用的類和方法。此外,我們還了解了如何使用第三方插件來自動化遷移過程。