DevFix
Spring Boot已验证

Spring Boot CORS 配了还是跨域?Spring Security 和 allowCredentials 的坑

@debug_master更新于 3 天前阅读 6 min0

一句话先说结论: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.

根因分析

三种主要的配置路径,选错或配漏都会失效:

  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 里写几个键就完事——那不会注册任何东西。

来源

最后更新于 2026-08-22

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法