你都配了 CORS,浏览器还是不让过?那到底是谁在“卡你脖子”?

举报
bug菌 发表于 2026/01/13 11:37:11 2026/01/13
【摘要】 🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。  本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。...

🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
  
本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】https://blog.csdn.net/weixin_43970743/article/details/151115907,你想学习的都被收集在内,快速投入学习!!两不误。
  
若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》:https://blog.csdn.net/weixin_43970743/category_11599389.html,涵盖SpringBoot所有版本教学文章。

演示环境说明:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
  • Spring Boot版本:3.5.4(于25年7月24日发布)
  • Maven版本:3.8.2 (或更高)
  • Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
  • 操作系统:Windows 11

1) 浏览器同源策略解析:CORS 不是“后端功能”,是浏览器的安全闸门

先把一个常见误会掰正:CORS 是浏览器的安全机制
你用 Postman / curl 调接口能 200,并不代表前端也能调通——因为它们不受浏览器同源策略约束。

MDN 对同源策略的解释很清楚:同源策略限制不同源之间的交互,跨源“读操作”通常不被允许;某些跨源请求还会触发预检(preflight)。

1.1 “同源”到底怎么判?

同源 = 协议 + 域名 + 端口都一致。
比如:

  • http://localhost:3000http://localhost:8080 ——端口不同,不同源
  • https://api.example.comhttps://www.example.com ——子域不同,不同源

1.2 什么情况下会触发 Preflight(OPTIONS)?

简单理解:当浏览器觉得你的请求“有点危险/不太普通”时,就会先发一个 OPTIONS 去问:“我能不能这么玩?”
如果预检过不了,真正的 GET/POST 根本不会发出去

Spring Framework 文档也明确:要启用跨域请求,必须有显式 CORS 配置;如果找不到匹配配置,预检会被拒绝。

2) @CrossOrigin vs 全局 WebMvcConfigurer:一个像“贴创可贴”,一个像“装门禁”

2.1 @CrossOrigin:局部、快、但容易散

适合:

  • 少数接口临时开放
  • Demo/PoC
  • 你明确只想放开某个 Controller 或方法

典型写法:

@RestController
@RequestMapping("/api")
@CrossOrigin(
  origins = "http://localhost:3000",
  allowedHeaders = "*",
  methods = {RequestMethod.GET, RequestMethod.POST},
  allowCredentials = "true"
)
public class FooController {
  @GetMapping("/foo")
  public String foo() { return "ok"; }
}

缺点(真实项目很常见):

  • 配置分散到处都是,半年后你自己都不敢删🙂
  • 很容易漏配:某个新接口忘了加注解,前端就炸
  • 更难表达“按路径统一治理”的策略

2.2 WebMvcConfigurer#addCorsMappings:全局治理、结构清晰(推荐主力用它)

Spring MVC 的 CORS 机制会在请求映射到 handler 后检查 CORS 配置:预检请求会被直接处理,实际请求会被拦截校验并写入响应头。

全局配置示例(Spring Boot 3 / Spring MVC):

@Configuration
public class CorsWebMvcConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**")
        .allowedOrigins("http://localhost:3000")
        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
        .allowedHeaders("*")
        .exposedHeaders("Location") // 需要前端读到的响应头
        .allowCredentials(true)
        .maxAge(3600);
  }
}

一句很要命的提醒
如果 allowCredentials(true),你就别再写 allowedOrigins("*") 这种“全放开”——浏览器会直接拒绝(这是 CORS 规范层面的限制,属于“你想放我也不让你放”的那种😅)。
(生产建议用明确域名列表,或 allowedOriginPatterns 做模式匹配。)

3) Spring Security 层面配置 CORS 的优先级陷阱:你以为是 MVC 配置没生效,其实是安全链先把你按住了

这是最经典的“明明我配了,为啥不行”的根源:
你在 Spring MVC 配了 CORS,但 Spring Security 过滤器链更早执行,直接把预检/跨域请求挡了

Spring Security 官方文档强调:确保 CORS 最先被处理的最简单方式是使用 CorsFilter;并且 Spring Security 只有在存在 UrlBasedCorsConfigurationSource 时才会自动配置 CORS。

换句话说:

  • 只配 WebMvcConfigurer:MVC 认识 CORS,但 Security 可能不配合
  • Security 没启用 cors() 或没提供 CorsConfigurationSource:预检可能被 401/403 干掉
  • 然后浏览器报 CORS 错,你还以为是“响应头没加上”……其实是你根本没走到 MVC 那一步🙂

3.1 推荐:在 SecurityFilterChain 里显式启用 CORS + 提供配置源

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .cors(cors -> {})              // ✅ 启用 CORS(交给 CorsConfigurationSource)
      .csrf(csrf -> csrf.disable())  // 示例:按需开启/关闭
      .authorizeHttpRequests(auth -> auth
          .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // ✅ 预检放行
          .requestMatchers("/api/**").authenticated()
          .anyRequest().permitAll()
      );
    return http.build();
  }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(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("/api/**", config);
    return source;
  }
}

这套做法对“优先级陷阱”非常有效,因为它满足了 Spring Security 文档说的关键点:

  • CORS 由 CorsFilter / Security 集成优先处理
  • 存在 UrlBasedCorsConfigurationSource 时,Security 才会自动配置 CORS

你会发现:同样是 CORS 配置,放在 Security 侧更“靠谱”,因为它就在请求入口的过滤器链上,能保证预检不被误伤。

4) 处理 Preflight Request(OPTIONS):别让预检死在 401/403 上

Spring Framework 文档明确:如果没有匹配的 CORS 配置,预检会被拒绝
而在开启 Spring Security 时,预检经常死于两类问题:

4.1 典型死法 1:OPTIONS 被要求认证

浏览器发预检通常不带认证信息(或至少你不该依赖它),结果 Security 看见没登录就 401/403。
解决:显式放行 OPTIONS(上面已经示例了)。

.authorizeHttpRequests(auth -> auth
  .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
  // ...
)

4.2 典型死法 2:预检的 Allowed-Headers / Allowed-Methods 对不上

浏览器预检会带:

  • Access-Control-Request-Method
  • Access-Control-Request-Headers

如果你的后端没允许这些 header/method,预检失败,前端就只剩一句“CORS error”(非常不友好🙂)。

建议你在开发阶段先放宽:

  • allowedHeaders("*")
  • methods 把你可能用到的都列上(尤其别忘了 OPTIONS)

上线再收紧(别一上来就“极致安全”,最后你会“极致痛苦”😂)。

一套“少踩坑”的推荐策略(我自己的偏好,供你参考🙂)

  1. 主力走全局配置:要么 WebMvcConfigurer,要么(更推荐)Security 侧的 CorsConfigurationSource
  2. 只在个别接口用 @CrossOrigin:比如临时开放、第三方回调、单独策略
  3. Security 一定显式启用 cors(),并提供 UrlBasedCorsConfigurationSource
  4. 预检 OPTIONS 放行(至少对你开放跨域的路径)
  5. allowCredentials(true) 时,Origin 不能是 “*”(用明确域名或 pattern)

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。

ps:本文涉及所有源代码,均已上传至Gitee:https://gitee.com/bugjun01/SpringBoot-demo 开源,供同学们一对一参考 Gitee传送门https://gitee.com/bugjun01/SpringBoot-demo,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗

🫵 Who am I?

我是 bug菌:

  • 热活跃于 CSDN:https://blog.csdn.net/weixin_43970743 | 掘金:https://juejin.cn/user/695333581765240 | InfoQ:https://www.infoq.cn/profile/4F581734D60B28/publish | 51CTO:https://blog.51cto.com/u_15700751 | 华为云:https://bbs.huaweicloud.com/community/usersnew/id_1582617489455371 | 阿里云:https://developer.aliyun.com/profile/uolxikq5k3gke | 腾讯云:https://cloud.tencent.com/developer/user/10216480/articles 等技术社区;
  • CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
  • 掘金、InfoQ、51CTO 等平台签约及优质作者;
  • 全网粉丝累计 30w+

更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看:https://bbs.csdn.net/topics/612438251 👈️
硬核技术公众号 「猿圈奇妙屋」https://bbs.csdn.net/topics/612438251 期待你的加入,一起进阶、一起打怪升级。

- End -

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。