CORS与前后端联调-WebMvcConfig里的一行配置
2026/9/24 3:51:42 网站建设 项目流程

08-CORS与前后端联调-WebMvcConfig里的一行配置

系列:AI 伙伴(AI-Partner)——具身智能陪伴机器人 · 数据接口部署与二次开发篇(08/12)

一、先搞懂:为什么浏览器要"多管闲事"

你本地把 AI 伙伴(AI-Partner)后端跑在 8080 端口,又用前端页面开在 5500 端口调试,结果浏览器控制台一抹红:

Access to XMLHttpRequest at 'http://localhost:8080/api/chat' from origin 'http://localhost:5500' has been blocked by CORS policy

很多新手的反应是"接口坏了"。其实接口好得很,是浏览器在拦你。这就是同源策略(Same-Origin Policy)。

同源的定义很严格:协议、域名、端口三者全部相同才算同源。http://localhost:8080http://localhost:5500域名相同但端口不同——不同源。浏览器规定:不同源的情况下,页面里的 JS可以发出请求,但读不到响应(更早一步,某些请求连发都发不出去,后面讲预检)。这是浏览器的安全机制,防止恶意网站拿着你的登录态去偷偷请求别的站点。

关键词:这是浏览器的行为。所以 curl、Postman、服务器之间的 HTTP 调用统统没有跨域问题——它们不经过浏览器。跨域只折磨"浏览器里跑的页面"。谁来解决?服务端声明"我允许谁来访问我",这就是 CORS(跨域资源共享)。

二、项目里的 CORS 配置:逐行解读

AI 伙伴(AI-Partner)的 CORS 配置在config/WebMvcConfig里,短小精悍(项目源码,全文如下):

@ConfigurationpublicclassWebMvcConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddCorsMappings(CorsRegistryregistry){registry.addMapping("/api/**").allowedOriginPatterns("*").allowedMethods("GET","POST","PUT","DELETE","OPTIONS").allowedHeaders("*").maxAge(3600);}}

逐项拆解:

配置项项目取值含义
addMapping/api/**只对/api前缀的接口生效,其他路径(如根路径元信息接口)不管
allowedOriginPatterns*允许任意来源,且是"模式匹配"式通配
allowedMethodsGET/POST/PUT/DELETE/OPTIONS放行的 HTTP 方法白名单
allowedHeaders*请求头全放行(如 Content-Type、自定义 token 头)
maxAge3600预检结果缓存 1 小时,见后文

注意这个项目没有设置allowCredentials(true),所以*在这里不违法。但一旦要带 Cookie(比如以后加会话鉴权),情况就完全变了,往下看。

三、allowedOriginPatterns(““) 与 allowedOrigins(””):关键差异

这两个方法长得像双胞胎,行为却有本质区别,也是 Spring 升级后最常见的报错来源:

方法语义与 allowCredentials(true) 组合
allowedOrigins("*")响应头原样回Access-Control-Allow-Origin: *非法。浏览器规定带凭证时响应头必须是具体域名,不允许*,Spring 会直接抛异常
allowedOriginPatterns("*")“任意来源都匹配”,但响应头回显请求方的真实 Origin(如http://localhost:5500合法。浏览器拿到具体域名,允许带凭证

换句话说:allowedOrigins("*")是"我对所有人说随便来";allowedOriginPatterns("*")是"谁来了我就对谁说你可以来"——效果上都是全放行,但后者能和 Cookie 组合使用。Spring Boot 2.4 之后如果你写allowedOrigins("*")又配了allowCredentials(true),启动就报When allowCredentials is true, allowedOrigins cannot contain the special value "*"

AI 伙伴(AI-Partner)选 patterns 的写法,等于给未来的 Cookie 鉴权预留了兼容性——虽然当前项目没有拦截器和凭证校验,这个选择依然是对的姿势。

顺带把安全这盆冷水泼了:全开 CORS 只适合开发联调。项目文档里也如实把"收紧 CORS、加鉴权"列进了生产建议。上生产前务必改白名单,文末给写法。

四、预检请求(OPTIONS)流程拆解

为什么前端一个"普通 POST"会变成两个请求?因为浏览器把跨域请求分两类:

  • 简单请求:方法为 GET/HEAD/POST,且请求头都在安全列表内、Content-Type 仅限text/plainmultipart/form-dataapplication/x-www-form-urlencoded——直接发。
  • 非简单请求:比如你的前端习惯性发Content-Type: application/json的 POST(AI 伙伴(AI-Partner)的/api/chat就是这种)——浏览器会先发一个 OPTIONS 预检请求问服务器"我能发吗",得到许可再发真正的请求。

完整时序(演示):

浏览器 后端(8080) │ ── OPTIONS /api/chat ───────▶│ 预检:不带业务数据 │ Origin: http://localhost:5500 │ Access-Control-Request-Method: POST │ ◀── 200 + CORS 响应头 ───────│ │ Access-Control-Allow-Origin: http://localhost:5500 │ Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS │ Access-Control-Max-Age: 3600 │ ── POST /api/chat (JSON) ───▶│ 真实请求 │ ◀── {"code":0,...} ──────────│

两个实战要点:

  1. maxAge(3600)的意义:预检结果缓存 1 小时,期间同一接口的跨域请求不再重复发 OPTIONS,省一半往返。缓存期内你改了 CORS 配置却发现"没生效",先清缓存或换个无痕窗口再排查。
  2. OPTIONS 请求本身不经过你的业务代码,所以不要在 Controller 里试图"接住"它,Spring MVC 会自动应答。

五、小程序与网页调试为什么会遇到跨域

联调场景逐个说:

调试方式有跨域问题吗原因
curl / Postman没有不经过浏览器
本地网页(file:// 或别的端口)经典跨域场景,服务端配置 CORS 解决
微信小程序开发者工具通常没有,但体验版/真机可能遇到小程序用wx.request,默认不检查 CORS;但开发工具勾选"不校验合法域名"时走的是浏览器内核模拟,部分场景仍会撞上
Nginx 反代后的前端没有(同域)前端和/api都从同一个域名进出,浏览器认为是同源

这也是为什么很多团队最终选择 Nginx 把前端静态资源和后端接口收进同一个域名——跨域问题从物理上消失。而开发期最省事的方案就是像 AI 伙伴(AI-Partner)这样后端直接放开。

另外提醒一句:项目后端调用的视觉服务(FastAPI)自己也开了 CORS 全开(allow_origins=["*"]),如果你想让浏览器页面直连 8000 端口的视觉接口调试,也不会被拦——但同理,这是开发态配置。

六、生产环境:把*收紧成白名单(示意)

生产环境的正确写法是把域名写死(示意):

@OverridepublicvoidaddCorsMappings(CorsRegistryregistry){registry.addMapping("/api/**").allowedOriginPatterns("https://www.example.com","https://m.example.com").allowedMethods("GET","POST","PUT","DELETE").allowedHeaders("Content-Type","Authorization").maxAge(3600);}

再讲究一点,可以把域名列表放进配置文件或环境变量,按spring.profiles区分 dev/prod——开发全开、生产白名单。记住三条红线:

  1. 生产不要allowedOriginPatterns("*"),尤其加了 Cookie/Token 凭证之后;
  2. allowedHeaders*收敛到实际用到的头,少暴露一个是一个;
  3. 若用了 Nginx 统一反代,后端 CORS 甚至可以直接关掉,让网关统一管。

七、跨域排查表:报错 → 原因 → 解决

收好这张表,联调遇到 CORS 报错按图索骥:

报错信息(关键词)原因解决
No 'Access-Control-Allow-Origin' header is present服务端没配 CORS,或请求路径不在addMapping范围内确认配置覆盖了目标路径;确认后端真的重启了
has been blocked by CORS policy: Response to preflight request doesn't pass access control check预检(OPTIONS)被拒:方法/头不在白名单,或被拦截器挡了检查allowedMethods/allowedHeaders;本项目暂无拦截器,加了鉴权后要给 OPTIONS 放行
The value of 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'前端带 Cookie(credentials: ‘include’),服务端却回*allowedOriginPatterns替代allowedOrigins,并确认allowCredentials配置一致
Method PUT is not allowed by Access-Control-Allow-Methods方法没进白名单白名单里补上该方法
Request header field xxx is not allowed自定义请求头没放行allowedHeaders里加该头
改了配置依旧报错预检缓存 1 小时未过期 / 浏览器缓存 / 配置没加载清浏览器缓存或换无痕窗口;启动日志确认 WebMvcConfig 生效
本地没问题,上生产报跨域生产走了 Nginx/网关,CORS 头被网关拦截或重复添加检查网关层是否也配了 CORS,避免响应头重复

八、收尾

CORS 这件事,本质是浏览器和服务端的一次"握手确认":浏览器问"你允许我访问吗",服务端答"允许谁、用什么方法、带什么头、答复有效期多久"。AI 伙伴(AI-Partner)用WebMvcConfig里不到 10 行的配置换来了前后端联调的丝滑,开发期这是甜;但它同时意味着任何网页都能调你的/api/**——生产期这就是裸奔。联调放开,上线收紧,这是纪律。配合下一阶段的鉴权和网关改造,把"门"真正装上门锁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询