知識庫 / Spring / Spring Security RSS 訂閱

Spring Security:WebSecurityConfigurerAdapter 升級指南

Spring Security
HongKong
10
11:52 AM · Dec 06 ,2025

1. 概述

Spring Security 允許通過擴展 WebSecurityConfigurerAdapter 類來定製 HTTP 安全功能,例如端點授權或身份驗證管理器配置。然而,在最近的版本中,Spring 已棄用此方法,並鼓勵基於組件的安全性配置。

在本教程中,我們將學習如何在 Spring Boot 應用程序中替換此棄用方法,並運行一些 MVC 測試。

2. Spring Security 不使用 WebSecurityConfigurerAdapter

我們經常看到擴展 WebSecurityConfigurerAdapter 類的 Spring HTTP 安全配置類。

從 5.7.0-M2 版本開始,Spring 廢棄了使用 WebSecurityConfigurerAdapter 的方式,並建議創建不使用它的配置。

讓我們通過創建一個使用內存身份驗證的 Spring Boot 應用程序來演示這種新的配置類型。

首先,我們將定義我們的配置類:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    // config

}

我們將添加方法安全註解,以支持基於不同角色的處理。

2.1. 配置身份驗證

使用 <em >WebSecurityConfigureAdapter</em >,我們將使用 `AuthenticationManagerBuilder> 來設置我們的身份驗證上下文。

為了避免被棄用,我們可以定義 <em >UserDetailsManager</em >> 或UserDetailsService> 組件:`

@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;
}

或者,藉助我們提供的 UserDetailService,我們甚至可以設置一個 AuthenticationManager

@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) 
  throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
      .userDetailsService(userDetailsService)
      .passwordEncoder(bCryptPasswordEncoder)
      .and()
      .build();
}

同樣,如果使用 JDBC 或 LDAP 身份驗證,也會正常工作。

2.2. 配置 HTTP 安全

更重要的是,為了避免 HTTP 安全相關功能的棄用,我們可以創建一個 SecurityFilterChain Bean。

例如,假設我們希望根據角色來保護某些端點,並將匿名入口僅用於登錄。我們還將限制任何刪除請求到管理員角色。我們將使用 Basic Authentication:

@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();
}

HTTP 安全將創建一個 DefaultSecurityFilterChain 對象,用於加載請求匹配器和過濾器。

2.3. 配置 Web 安全性

對於 Web 安全性,我們現在可以使用回調接口 WebSecurityCustomizer

我們將添加一個調試級別以及忽略一些路徑,例如圖像或腳本:

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

3. Endpoints 控制器

現在我們將為我們的應用程序定義一個簡單的 REST 控制器類:

@RestController
public class ResourceController {
    @GetMapping("/login")
    public String loginEndpoint() {
        return "Login!";
    }

    @GetMapping("/admin")
    public String adminEndpoint() {
        return "Admin!";
    }

    @GetMapping("/user")
    public String userEndpoint() {
        return "User!";
    }

    @GetMapping("/all")
    public String allRolesEndpoint() {
        return "All Roles!";
    }

    @DeleteMapping("/delete")
    public String deleteEndpoint(@RequestBody String s) {
        return "I am deleting " + s;
    }
}

正如我們之前提到的,在定義 HTTP 安全性時,我們將添加一個通用的 /login 端點,該端點可供任何人訪問,以及針對管理員和用户的特定端點,以及一個未通過角色驗證但仍需要身份驗證的 /all 端點。

4. 測試端點

讓我們使用 MVC 模擬測試來將新的配置應用到 Spring Boot 測試中,以測試我們的端點。

4.1. 測試匿名用户

匿名用户可以訪問 login</em/> 終點。如果他們嘗試訪問其他資源,將會被拒絕訪問 (401</em/>):

@Test
@WithAnonymousUser
public void whenAnonymousAccessLogin_thenOk() throws Exception {
    mvc.perform(get("/login"))
      .andExpect(status().isOk());
}

@Test
@WithAnonymousUser
public void whenAnonymousAccessRestrictedEndpoint_thenIsUnauthorized() throws Exception {
    mvc.perform(get("/all"))
      .andExpect(status().isUnauthorized());
}

此外,除 /login 端點之外,所有端點都需要進行身份驗證,例如 /all 端點。

4.2. 測試用户角色

用户角色可以訪問通用的端點以及為該角色授予的所有其他路徑:

@Test
@WithUserDetails()
public void whenUserAccessUserSecuredEndpoint_thenOk() throws Exception {
    mvc.perform(get("/user"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails()
public void whenUserAccessRestrictedEndpoint_thenOk() throws Exception {
    mvc.perform(get("/all"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails()
public void whenUserAccessAdminSecuredEndpoint_thenIsForbidden() throws Exception {
    mvc.perform(get("/admin"))
      .andExpect(status().isForbidden());
}

@Test
@WithUserDetails()
public void whenUserAccessDeleteSecuredEndpoint_thenIsForbidden() throws Exception {
    mvc.perform(delete("/delete"))
      .andExpect(status().isForbidden());
}

值得注意的是,如果用户角色嘗試訪問受管理員保護的端點,用户將收到“禁止訪問” (403) 錯誤。

相反,如果沒有憑據,例如在先前示例中提到的匿名用户,將會收到“未授權”錯誤 (401)。

4.3. 測試管理員角色

正如我們所見,擁有管理員角色的用户可以訪問任何端點:

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessUserEndpoint_thenOk() throws Exception {
    mvc.perform(get("/user"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessAdminSecuredEndpoint_thenIsOk() throws Exception {
    mvc.perform(get("/admin"))
      .andExpect(status().isOk());
}

@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessDeleteSecuredEndpoint_thenIsOk() throws Exception {
    mvc.perform(delete("/delete").content("{}"))
      .andExpect(status().isOk());
}

5. 處理已棄用的 csrf()requiresChannel()

使用最近的 Spring Security 更新後,用於 HTTP 安全配置的一些方法,如 csrf()requiresChannel(),存在已過時的版本,在遷移到最新版本的 Spring Boot 時需要進行調整。 在本節中,我們將討論使用 lambda 表達式配置這些方法的更新方式。

CSRF 保護設置,以前通過簡單的調用 http.csrf().disable() 完成,現在已調整為 lambda 語法:

http.csrf(csrf -> csrf.disable());

採用這種方法,我們以一種與最近的 Spring Security 更新保持一致的功能方式來指定 CSRF 配置。

同樣,用於強制 HTTPS 的 requiresChannel() 方法也已移動到基於 lambda 的格式:

http.requiresChannel(channel -> channel.anyRequest().requiresSecure());

我們可以在單個 SecurityFilterChain Bean 中包含更新的 csrfrequiresChannel 配置。 此設置強制所有請求使用 HTTPS,並出於簡單起見,禁用 CSRF:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf->csrf.disable())
      .requiresChannel(channel -> channel.anyRequest().requiresSecure())
      .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
        uthorizationManagerRequestMatcherRegistry.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();
}

6. 結論

在本文中,我們學習瞭如何在不使用 WebSecurityConfigureAdapter 的情況下創建 Spring Security 配置,並在此過程中創建用於身份驗證、HTTP 安全和 Web 安全的組件。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.