測試 Spring Boot 中的 CORS 跨域資源共享

Spring Security,Testing
Remote
1
12:16 PM · Nov 30 ,2025

1. 概述

跨域資源共享 (CORS) 是一種安全機制,允許一個網頁從一個源訪問另一個源的資源。 瀏覽器強制執行此機制以防止網站向不同域發出未經授權的請求。

在構建 Spring Boot 應用程序時,重要的是要正確測試我們的 CORS 配置,以確保我們的應用程序可以安全地與授權源交互,同時阻止未經授權的源。

通常情況下,我們只在部署應用程序後才會發現 CORS 問題。通過在開發過程中儘早測試我們的 CORS 配置,我們可以找到並修復這些問題,從而節省時間和精力。

在本教程中,我們將探討如何編寫有效的測試以使用 MockMvc 來驗證我們的 CORS 配置。

2. 配置 Spring Boot 中的 CORS

在 Spring Boot 應用程序中配置 CORS 有多種方法。對於本教程,我們將使用 Spring Security 並定義一個 CorsConfigurationSource

private CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowedOrigins(List.of("https://baeldung.com"));
    corsConfiguration.setAllowedMethods(List.of("GET"));
    corsConfiguration.setAllowedHeaders(List.of("X-Baeldung-Key"));
    corsConfiguration.setExposedHeaders(List.of("X-Rate-Limit-Remaining"));

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    return source;
}

在我們的配置中,我們允許來自 https://baeldung.com 域名的請求,使用 GET 方法,X-Baeldung-Key 標頭,並暴露 X-Rate-Limit-Remaining 標頭到響應中

我們已經在配置中硬編碼了這些值,但我們可以使用 @ConfigurationProperties 來外部化它們。

接下來,讓我們配置 SecurityFilterChain bean 以應用我們的 CORS 配置:

private static final String[] WHITELISTED_API_ENDPOINTS = { "/api/v1/joke" };

@Bean
public SecurityFilterChain configure(HttpSecurity http) {
    http
      .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource()))
      .authorizeHttpRequests(authManager -> {
        authManager.requestMatchers(WHITELISTED_API_ENDPOINTS)
          .permitAll()
          .anyRequest()
          .authenticated();
      });
    return http.build();
}

在這裏,我們使用 corsConfigurationSource() 方法定義的 CORS 配置。

我們還白名單了 /api/v1/joke 端點,以便它可以無需身份驗證地訪問。我們將使用此 API 端點作為測試我們的 CORS 配置的基礎:

private static final Faker FAKER = new Faker();

@GetMapping(value = "/api/v1/joke")
public ResponseEntity<JokeResponse> generate() {
    String joke = FAKER.joke().pun();
    String remainingLimit = FAKER.number().digit();

    return ResponseEntity.ok()
      .header("X-Rate-Limit-Remaining", remainingLimit)
      .body(new JokeResponse(joke));
}

record JokeResponse(String joke) {};

我們使用 Datafaker 生成一個隨機笑話和一個剩餘速率限制值。然後,我們返回笑話到響應主體中,幷包含 X-Rate-Limit-Remaining 標頭,其中包含生成的價值。

3. 測試 CORS 使用 MockMvc

現在我們已經配置了應用程序中的 CORS,讓我們編寫一些測試,以確保它按預期工作。我們將使用 MockMvc 來向我們的 API 端點發送請求並驗證響應

3.1. 測試允許的源頭

首先,讓我們測試從允許的源頭髮送的請求是否成功

mockMvc.perform(get("/api/v1/joke")
  .header("Origin", "https://baeldung.com"))
  .andExpect(status().isOk())
  .andExpect(header().string("Access-Control-Allow-Origin", "https://baeldung.com"));

我們還驗證響應是否包含來自允許源頭請求的 Access-Control-Allow-Origin 標頭。

接下來,讓我們驗證非允許源頭請求是否被阻止:

mockMvc.perform(get("/api/v1/joke")
  .header("Origin", "https://non-baeldung.com"))
  .andExpect(status().isForbidden())
  .andExpect(header().doesNotExist("Access-Control-Allow-Origin"));

3.2. 測試允許的方法

為了測試允許的方法,我們將使用 HTTP OPTIONS 方法模擬預飛行請求

mockMvc.perform(options("/api/v1/joke")
  .header("Origin", "https://baeldung.com")
  .header("Access-Control-Request-Method", "GET"))
  .andExpect(status().isOk())
  .andExpect(header().string("Access-Control-Allow-Methods", "GET"));

我們驗證請求是否成功並且 Access-Control-Allow-Methods 標頭在響應中存在。

同樣,讓我們確保非允許的方法被拒絕:

mockMvc.perform(options("/api/v1/joke")
  .header("Origin", "https://baeldung.com")
  .header("Access-Control-Request-Method", "POST"))
  .andExpect(status().isForbidden());

3.3. 測試允許的標頭

現在,我們將通過使用 Access-Control-Request-Headers 標頭髮送預飛行請求並驗證響應中的 Access-Control-Allow-Headers 來測試允許的標頭

mockMvc.perform(options("/api/v1/joke")
  .header("Origin", "https://baeldung.com")
  .header("Access-Control-Request-Method", "GET")
  .header("Access-Control-Request-Headers", "X-Baeldung-Key"))
  .andExpect(status().isOk())
  .andExpect(header().string("Access-Control-Allow-Headers", "X-Baeldung-Key"));

並且我們驗證我們的應用程序拒絕非允許的標頭:

mockMvc.perform(options("/api/v1/joke")
  .header("Origin", "https://baeldung.com")
  .header("Access-Control-Request-Method", "GET")
  .header("Access-Control-Request-Headers", "X-Non-Baeldung-Key"))
  .andExpect(status().isForbidden());

3.4. 測試暴露的標頭

最後,我們將測試暴露的標頭在允許源頭響應中是否正確包含,

mockMvc.perform(get("/api/v1/joke")
  .header("Origin", "https://baeldung.com"))
  .andExpect(status().isOk())
  .andExpect(header().string("Access-Control-Expose-Headers", "X-Rate-Limit-Remaining"))
  .andExpect(header().exists("X-Rate-Limit-Remaining"));

我們驗證 Access-Control-Expose-Headers 標頭在響應中存在並且包含我們的暴露標頭 X-Rate-Limit-Remaining。我們還檢查實際的 X-Rate-Limit-Remaining 標頭是否存在。

同樣,讓我們確保我們的暴露標頭在非允許源頭響應中未包含:

mockMvc.perform(get("/api/v1/joke")
  .header("Origin", "https://non-baeldung.com"))
  .andExpect(status().isForbidden())
  .andExpect(header().doesNotExist("Access-Control-Expose-Headers"))
  .andExpect(header().doesNotExist("X-Rate-Limit-Remaining"));

4. 結論

在本文中,我們討論瞭如何使用 MockMvc 來編寫有效的測試,以驗證我們的 CORS 配置是否正確地允許來自授權源、方法和標頭,同時阻止未授權請求。

通過徹底測試我們的 CORS 配置,我們可以儘早發現配置錯誤,並防止生產環境中出現意外的 CORS 錯誤。

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

發佈 評論

Some HTML is okay.