在 Spring Boot 中測試 CORS
1. 概述
跨來源資源共享 (CORS) 是一種安全機制,允許一個來源的網頁存取另一個來源的資源。它由瀏覽器強制執行,以防止網站向不同網域發出未經授權的請求。
使用 Spring Boot 建立 Web 應用程式時,正確測試我們的 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. 使用 MockMvc 測試 CORS
現在我們已經在應用程式中配置了 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 錯誤。
與往常一樣,本文中使用的所有程式碼範例都可以在 GitHub 上找到。