一句話先說結論:CORS 報錯通常不是後端數據出問題,而是瀏覽器在攔——服務器返回了數據,但響應頭上沒有
Access-Control-Allow-Origin。Spring Boot 預設不加 CORS 頭,必須顯式配;最常見的翻車場景有兩個:一是加了 Spring Security 後原來的 MVC CORS 設定“失效”,二是allowCredentials(true)和allowedOrigins("*")撞在一起被拒絕。
背景
CORS 是瀏覽器基於“同源策略”的放行機制:源(協議 + 域名 + 端口)不同就預設攔。前端跑在 http://localhost:3000,後端在 http://localhost:8080,兩者端口不同就是跨域。跨域請求裡,帶自定義頭、Content-Type: application/json、或用 PUT/DELETE 的,瀏覽器會先發一個 OPTIONS 預檢請求,問服務器“你允不允許”。
這解釋了為什麼 curl/Postman 能通而瀏覽器報錯——curl 不執行 CORS,瀏覽器才執行。
現象
前端控制台:
Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
或者加了 Spring Security 之後,預檢返回 401 且不帶 CORS 頭:
Cross-Origin Request Blocked: ... (Reason: CORS header
'Access-Control-Allow-Origin' missing). Status code: 401.
啟動時 allowCredentials + allowedOrigins(“*”) 的報錯
如果設定了 allowCredentials(true) 和 allowedOrigins("*"),應用啟動時直接失敗:
java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
at org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:509)
at org.springframework.web.cors.CorsConfiguration.checkOrigin(CorsConfiguration.java:345)
at org.springframework.web.servlet.config.annotation.CorsRegistration.checkConfiguration(CorsRegistration.java:102)
at org.springframework.web.servlet.config.annotation.CorsRegistration.build(CorsRegistration.java:94)
at org.springframework.web.servlet.config.annotation.CorsRegistry.getCorsConfigurations(CorsRegistry.java:128)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.setConfigurers(WebMvcConfigurationSupport.java:556)
at org.springframework.web.servlet.config.annotation.AbstractBeanFactoryPointcutAdvisor.invoke(AbstractBeanFactoryPointcutAdvisor.java:52)
Security 攔截 OPTIONS 返回 401 的日誌
2026-08-22 10:30:15.042 DEBUG 12345 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : Securing OPTIONS /api/users
2026-08-22 10:30:15.045 DEBUG 12345 --- [nio-8080-exec-3] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
2026-08-22 10:30:15.046 DEBUG 12345 --- [nio-8080-exec-3] o.s.s.w.c.HttpSessionRequestCache : Did not save request since it did not match one of the configured defaultRequestMatcher(s)
2026-08-22 10:30:15.046 DEBUG 12345 --- [nio-8080-exec-3] o.s.s.w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2026-08-22 10:30:15.047 DEBUG 12345 --- [nio-8080-exec-3] o.s.s.w.a.HttpSessionSecurityContextRepository : Did not store security context since it was empty
2026-08-22 10:30:15.047 DEBUG 12345 --- [nio-8080-exec-3] w.o.c.e.ExceptionTranslationFilter : Sending 403 Forbidden access denied response from AccessDeniedHandler
根因分析
三種主要的設定路徑,選錯或配漏都會失效:
@CrossOrigin(單控制器/方法,最快)WebMvcConfigurer.addCorsMappings(全局,只對 Spring MVC 生效)- Spring Security 的
http.cors()(集成 Security 時必須走這條)
最容易踩的是這幾個坑:
坑 1:只在 yml 裡寫 spring.web.cors.* 不會生效。那些設定只是數據源,本身不註冊任何過濾器或攔截器,純 YAML 不等於開箱即用。
坑 2:加了 Spring Security 後,MVC 的 CORS 設定“突然失效”。原因是 Security 的過濾器鏈跑在控制器之前,OPTIONS 預檢請求還沒到 MVC 就被當成未認證請求攔下,返回 401 且不帶 CORS 頭。此時光有 WebMvcConfigurer 沒用,必須在 Security 裡同時打開 http.cors() 並放行 OPTIONS。
坑 3:allowCredentials(true) 與 allowedOrigins("*") 不能共存。帶憑據(cookie/JWT)時瀏覽器要求 Access-Control-Allow-Origin 必須是具體源、不能是 *。Spring Boot 2.4+ 對這對組合會直接拒絕(啟動就報 IllegalArgumentException)。要麼列出具體 origin,要麼改用 allowedOriginPatterns("*")。
坑 4:自定義 JWT Filter 攔截了預檢。如果 OncePerRequestFilter 沒對 OPTIONS 放行,預檢流程會斷在它手裡。
解決方案
純 Spring MVC:WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
集成 Spring Security:SecurityFilterChain 裡配
這才是“加了 Security 還跨域”的正解:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 放行预检
.anyRequest().authenticated()
);
return http.build();
}
}
要點:http.cors(...) 顯式掛上 CORS 源,並把 OPTIONS 預檢 permitAll(),否則預檢永遠到不了 MVC。
關於 * 和憑據
- 不需要帶 cookie/token:可以直接
allowedOrigins("*")。 - 需要帶憑據:用
allowedOriginPatterns("*")或列出具體 origin,別用allowedOrigins("*")。
排查小抄
- 先看瀏覽器 Network:請求
Status = 0/ 標CORS error,是瀏覽器攔在了前面;返回4xx/5xx是已經到後端了。 - 看有沒有
OPTIONS請求、它是否返回 200 且帶 CORS 頭。 curl能通、瀏覽器不通 → 就是 CORS/策略問題,不是接口寫錯。- 確認後端日誌有沒有請求進來,能判斷是卡在瀏覽器還是卡在 Security。
小結
- CORS 是瀏覽器行為,後端要做的只是“把正確的響應頭帶上”。
- 配 CORS 先分清場景:單接口
@CrossOrigin,全局 MVCWebMvcConfigurer,有 Security 必須走http.cors()+ 放行OPTIONS。 - 帶憑據時別用
allowedOrigins("*"),換allowedOriginPatterns。 - 別指望 yml 裡寫幾個鍵就完事——那不會註冊任何東西。
References(參考來源)
- Fix CORS Errors in Spring Boot (3 Ways) — Amigoscodeblog
- Spring Boot CORS Configuration: @CrossOrigin vs WebMvcConfigurerblog
- How to Fix CORS in Spring Boot with CorsFilter and Spring Security — JavaThinkingblog
- CORS preflight blocked after Spring Boot 4 upgrade — entur/sobek issuegithub
- Spring Security Docs: CORS Integrationdocs
