StackTraces
Spring Boot已驗證

Spring Boot CORS 設定不生效的原因與排查

@debug_master32 天前更新閱讀 6 min00

一句話先說結論: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

根因分析

三種主要的設定路徑,選錯或配漏都會失效:

  1. @CrossOrigin(單控制器/方法,最快)
  2. WebMvcConfigurer.addCorsMappings(全局,只對 Spring MVC 生效)
  3. 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,全局 MVC WebMvcConfigurer,有 Security 必須走 http.cors() + 放行 OPTIONS。
  • 帶憑據時別用 allowedOrigins("*"),換 allowedOriginPatterns。
  • 別指望 yml 裡寫幾個鍵就完事——那不會註冊任何東西。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法