以下內容聚焦Spring Boot 3.x / Spring Security 6的當下實踐,幫你用最小代價拿下跨域。結論先行:前後端分離項目應將 CORS 作為“平台能力”,在MVC 層與Security 層雙棧一致配置,並對預檢請求(OPTIONS)提供零阻斷通道。🚀
一、目標與原則(結論速覽)
- 統一在 WebMvcConfigurer 與 SecurityFilterChain 開啓 CORS,保持配置同源。
- 對預檢請求放行;攜帶憑證時,不得使用
*,改用明確白名單域名。 - 通過 反向代理/網關 複用規則,杜絕環境漂移。
關鍵控制點:<span style="color:red">allowedOrigins / allowCredentials / allowedHeaders / allowedMethods / maxAge</span>。
二、代碼落地:全局 CORS(MVC 層)
// src/main/java/com/example/config/WebCorsConfig.java
@Configuration
public class WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://app.example.com", "https://admin.example.com")
.allowedMethods("GET","POST","PUT","DELETE","PATCH","OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Request-Id","X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
解釋:
addMapping("/**")作用到全部接口;allowedOrigins(...)明確白名單,滿足 <span style="color:red">Cookie/Authorization</span> 等憑證場景;allowCredentials(true)開啓跨站憑證;maxAge(3600)預檢緩存 1 小時,降低延遲;exposedHeaders(...)讓瀏覽器可讀自定義響應頭。
三、代碼落地:Security 層放行預檢
// src/main/java/com/example/config/SecurityConfig.java
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(Customizer.withDefaults()) // 關鍵:啓用 CORS
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 關鍵:放行預檢
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cfg = new CorsConfiguration();
cfg.setAllowedOrigins(List.of("https://app.example.com","https://admin.example.com"));
cfg.setAllowedMethods(List.of("GET","POST","PUT","DELETE","PATCH","OPTIONS"));
cfg.setAllowedHeaders(List.of("*"));
cfg.setExposedHeaders(List.of("X-Request-Id","X-Total-Count"));
cfg.setAllowCredentials(true);
cfg.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cfg);
return source;
}
}
解釋:
- Security 默認會在 CorsFilter 前進行鏈式處理;
cors(Customizer.withDefaults())會讀取CorsConfigurationSource。 - 放行
OPTIONS /**保證瀏覽器預檢不被鑑權攔截。 - MVC 與 Security 保持同一份域名白名單,避免“走 MVC 通不過 Security”的錯配。
- <span style="color:red">不要再使用過時的 WebSecurityConfigurerAdapter</span>。
四、局部快速解法(臨時/灰度)
// 僅在必要時對單個控制器/方法使用
@CrossOrigin(origins={"https://app.example.com"}, allowCredentials = "true",
maxAge = 3600, exposedHeaders = {"X-Request-Id"})
@RestController
@RequestMapping("/api/users")
public class UserController { /* ... */ }
解釋:
- 局部註解適合灰度或緊急放行;
- 大規模項目建議仍以全局配置為主,降低維護成本與合規風險。
五、網關/代理層(可選但推薦)
// Spring Cloud Gateway (application.yml)
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "https://app.example.com,https://admin.example.com"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
解釋:
- 在網關統一 CORS,可同時保護多語言後端;
- 與後端保持同源配置,避免“代理放開/後端收緊”的割裂。
六、分析説明表(vditor/Markdown 兼容)
配置項對照表
| 配置項 | 含義 | 建議值 | 關鍵風險 |
|---|---|---|---|
| <span style="color:red">allowedOrigins</span> | 允許的來源域 | 生產用明確域名 | 與 allowCredentials=true 時禁止使用 * |
| allowedMethods | 允許的方法 | 明確或 "*" |
過度放開需配合鑑權 |
| allowedHeaders | 請求可帶的頭 | "*" 或白名單 |
過度限制會導致預檢失敗 |
| <span style="color:red">exposedHeaders</span> | 前端可讀的響應頭 | 業務所需最小集 | 不暴露敏感頭 |
| <span style="color:red">allowCredentials</span> | 是否攜帶憑證 | 需要會話時設 true |
搭配白名單域 |
| <span style="color:red">maxAge</span> | 預檢緩存秒數 | 1800–7200 | 太小會增大延遲 |
預檢處理工作流程(簡化)
- 瀏覽器發現跨站 → 發起
OPTIONS預檢; - 代理/網關接收並回寫 CORS 頭;
- Security 放行
OPTIONS,不觸發認證; - MVC 返回
2xx+ CORS 允許頭; - 瀏覽器緩存預檢結果,在
maxAge內直接發實際請求。✅
七、快速自檢命令與解釋
# 預檢自測:確認 204/200 + 允許頭齊全
curl -i -X OPTIONS https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST"
# 實際請求自測(帶 Cookie)
curl -i -X POST https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Content-Type: application/json" \
-H "Cookie: SESSION=xxxx" \
--data '{"name":"alice"}'
解釋:
- 第一個請求模擬瀏覽器預檢,關注響應頭中是否包含 <span style="color:red">
Access-Control-Allow-Origin</span>、<span style="color:red">Access-Control-Allow-Credentials</span>、<span style="color:red">Access-Control-Allow-Headers</span>; - 第二個請求驗證攜帶憑證場景;若 302/401,説明被鑑權鏈攔截,應檢查 Security 放行策略與會話策略(如
SameSite=None; Secure)。
八、常見坑位與硬性結論
- <span style="color:red">憑證 + “*”不可並存</span>:
allowCredentials=true時,allowedOrigins需顯式域名。 - <span style="color:red">重定向會吞頭</span>:接口有 301/302/307/308 時,瀏覽器可能不傳遞預期 CORS 頭,務必返回最終 2xx。
- <span style="color:red">Cookie 策略</span>:跨站會話需設置
SameSite=None; Secure,否則瀏覽器丟 Cookie。 - <span style="color:red">代理雙寫</span>:Nginx/網關若追加 CORS 頭需與後端一致,避免衝突。
- <span style="color:red">方法白名單與權限模型</span>:CORS 放開不等於放權;敏感接口仍以鑑權/簽名作為最後防線。🛡️
九、面向未來的治理建議
- 將 CORS 規則沉澱為配置中心策略,按環境(dev/stage/prod)分組;
- 建立域名白名單變更流程,引入自動化自測(
curl OPTIONS)作為發佈前哨; - 在 API 網關處統一冪等預檢緩存策略,降低移動弱網抖動。📈
一句話總結:把 CORS 當作平台級能力,<span style="color:red">MVC 與 Security 同源配置、預檢零阻斷、憑證配白名單</span>,即可在 Spring Boot 中實現“無縫跨域”的穩定交付。💼✨