理解全局跨域CORS配置

CORS 与跨域:从误解到理解

整理自一次完整的问答过程。核心不是记住 CORS 怎么配,而是分清它属于哪一层问题。


一、一句话结论

CORS 是浏览器的一道"归还响应"的许可,不是身份认证,不是权限控制,也不是部署工具。

它只回答一个问题:这个页面,能不能读我返回的数据? 它不回答:你是谁?你能不能调?你该看到什么?


二、判定规则:什么叫"同源"

浏览器比较两个地址的三个值,全部相同才算同源:

|----|--------------------|----------------------------|
| 组成 | 说明 | 举例 |
| 协议 | http / https 不同即跨域 | http vs https |
| 主机 | 域名或 IP 不同即跨域 | app.com vs api.app.com |
| 端口 | 数字不同即跨域 | :5173 vs :8080 |

复制代码

关键认知:端口是独占资源。 一个端口同一时刻只能被一个进程监听。所以:

  • 两个进程 → 必然两个端口 → 必然不同源
  • 想把 Spring Boot 的端口改成 5173 来"凑同源" → 直接报 Port 5173 was already in use

改 server.port 只是在两个不同源之间挪位置,挪不出同源。


三、CORS 在请求链路的哪个位置

复制代码

三个要点:

  1. ①和②是两次完全独立的请求,方向、目标都不同
  1. 页面地址只决定 Origin 头的值,从不决定请求发给谁 ;请求发去哪只看 fetch() 里写的字符串
  1. 后端从未访问过 5173,它只监听自己的端口

后端到底执行了没有?

|---------------------------------------------|----------|----------------------------------|
| 现象 | 后端执行了吗 | 真实原因 |
| 控制台报 CORS 红字,Network 里请求 200 且响应体完整 | 执行了 | 浏览器把响应扣下,不给 JS 读 |
| OPTIONS 预检返回 200 但报 CORS 错 | 没执行 | 预检没过,真实请求压根没发 |
| ERR_CONNECTION_REFUSED / 超时 | 没执行 | 服务没启动,或端口写错 |
| 200 但 res.json() 报 Unexpected token '<' | 没执行 | 请求打到了 dev server,返回的是 index.html |
| 后端返回 404 且有 JSON 错误体 | 到了,但路由不对 | 路径拼错,比如少个 /api |

记住:CORS 失败不会让后端"不干活" 。简单请求下数据已经写进库了,只是 JS 读不到返回值。 唯一例外是非简单请求(带 Authorization、Content-Type: application/json 的 POST/PUT/DELETE)------ 预检不过,真实请求不会发出。


四、六个误解与纠正

误解 1:前后端的 URL 必须不同,所以才需要 CORS

纠正:可以完全相同,生产环境通常就是相同的。

CORS 不要求你分域,它只是"当确实分域时"的放行开关。开发期端口不同,是两个独立进程的结果,不是设计要求。

误解 2:用 CORS 可以区分管理员页面和普通用户页面

纠正:CORS 分辨不了"谁",它连"你是谁"都不知道。

它唯一能看到的是 Origin 请求头,只说明"请求来自哪个页面地址",而且 curl 一行就能伪造,毫无可信度。

真正区分权限靠登录凭证:

复制代码

核心观念:后端不相信"你从哪个页面来",只相信"你带了什么凭证"。 前端隐藏菜单、路由守卫都只是体验,不是权限------接口必须自己校验一次。

误解 3:地址都不一样了,后端怎么会处理这个请求

纠正:后端只监听自己的地址,不关心你在哪个端口看页面。

就像打电话:你拨 8080,8080 接了正常办事;来电显示上有你的号码(Origin),但办事不依赖你的号码是多少。

类比:Origin 是信封上的寄件地址,不是身份证。

误解 4:前端请求成功了,但后端要的是 8080,所以功能没实现

纠正 :先看 Network 面板里的 Request URL 到底是什么。

  • 是 8080/api → 后端一定在处理,功能是实现的,只是浏览器不给 JS 读响应
  • 是 5173/api(相对路径又没配代理)→ 后端根本没收到,这才叫没实现

误解 5:CORS 是把前后端"合并"到一起的工具

纠正:CORS 不搬文件、不转发请求,只回几个响应头。

真正做合并的是构建部署 (npm run build + Nginx/静态托管)或反向代理(真转发请求)。

|---------|-------------|-------|-----------|
| 机制 | 做了什么 | 浏览器看到 | 是否需要 CORS |
| 构建 + 部署 | 真的把文件放到一起 | 同一个源 | 不需要 |
| 反向代理 | 真的转发请求 | 以为同源 | 不需要 |
| CORS | 什么都没搬,只回响应头 | 两个源 | 这就是它 |

所以 CORS 不是"合并工具",而是合并不了时的 B 计划。

误解 6:给第三方提供接口,URL 不能一样,所以要配 CORS

纠正:两处都不对。

  1. URL 完全可以一样 ,第三方就调 https://app.com/api/open。给开放接口单独开前缀是为了配额、版本、审计,跟跨域无关。
  1. CORS 对第三方调用通常用不上:

|-----------------|------------------|----------------------------|
| 调用方式 | CORS 是否相关 | 准入靠什么 |
| 第三方服务端调你(占绝大多数) | 完全无关,没有浏览器参与 | API Key / 签名 / IP 白名单 / 限流 |
| 第三方网页 JS 直连(少见) | 相关,需白名单或通配 | 仍然靠 Token,CORS 只是放行 |

CORS 从来不决定"谁有权调用",只决定"浏览器愿不愿意把响应交给那个页面"。


五、三种部署形态与 CORS 处理

|-------|------------------------------------|-------------------------------|
| 场景 | 部署形态 | CORS 怎么处理 |
| 开发联调 | Vite 5173 + 后端 8080 | 二选一:前端 proxy 或后端白名单 |
| 生产·同域 | 静态文件 + Nginx 反代(页面与 /api 同域) | 不用配,天然同源 |
| 生产·分域 | app.com + api.app.com,或前端上 CDN | 后端必须配(回显具体源 + 处理 OPTIONS) |

达成"同源"只有三种方式

  1. 一个进程同时提供页面和接口(Spring Boot + static/,最直接)
  1. 反向代理统一入口(Nginx 对外 443,内部分发给两个进程)
  1. 前端打包成静态文件交给后端/Nginx 托管(本质是 1 或 2)

反过来说:只要浏览器直接访问两个不同端口,就一定跨域。

为什么会有多个坐标?

业务隔离靠路径,不靠换坐标。 一个域名装下所有业务完全没问题:

复制代码

坐标真正会被拆开,是被这四件事逼的------它们全都与"分清谁在调用"无关:

  • CDN 加速:静态资源要放边缘节点,Spring Boot 跑不到 CDN 上
  • 独立发版:前端一天十次,后端一周一次,放一起每次改样式都要重启 JVM
  • 安全边界:后端只开在内网,公网只暴露网关的 443
  • 开发工具链:Vite 是 Node 进程,Spring 是 JVM 进程,物理上不可能共用一个端口

多坐标是成本,不是收益。 每多一个坐标,就多一套 DNS、证书、CORS 白名单和排查时间。 默认一个坐标,能不拆就不拆。


六、实战:Spring Boot 3 项目

如果你用 Spring Boot 托管页面(static/、Thymeleaf、Swagger)

页面和接口都在 8080,同源,永远用不到 CORS,一行都不用写。

这是最省心的形态,继续用就好。

如果将来接入 Vite / React / Vue

一旦 npm run dev 起了 5173,跨域立刻出现。两种解法:

方案 A:前端代理(开发期最省事,后端零改动)

复制代码

代码里写 fetch('/api/users') 即可。浏览器看到的是 5173 发给 5173,同源,CORS 不触发。

注意:proxy 是 dev server 的功能,打包上线后不存在。生产环境还要再面对一次。

方案 B:后端白名单(生产环境的正解)

复制代码

两个必踩的坑

  1. allowedOrigins("*") 和 allowCredentials(true) 不能共存,浏览器直接拒绝。带 Cookie 必须回显具体来源。
  1. 必须能响应 OPTIONS 预检 ,否则带 Authorization 或 JSON body 的请求全部发不出去。

为什么"通常都在后端配置"是对的

因为 CORS 只能配在后端 ------它是响应头,前端写什么都改变不了浏览器的判定。 前端那个配置叫 proxy,作用恰恰相反:让后端不用配 CORS。

开发期二选一,生产期最终都落到后端。


七、排错三步法

  1. Network 面板 看 Request URL:是 8080 还是 5173,一眼定生死
  1. 看 Response 里有没有真实 JSON:有,说明后端干完活了
  1. 用 curl 打同一个地址:能拿到数据,说明问题只在浏览器这一层
复制代码

因为 curl 不是浏览器,没有同源策略,也没人拦它。这也再次说明 CORS 只是浏览器给自己定的规矩。


八、决策清单:我需要配 CORS 吗?

复制代码

九、记忆锚点

  • 同源 = 协议 + 主机 + 端口,三者全同
  • Origin = 信封上的寄件地址,不是身份证
  • CORS = 浏览器归还响应的许可证,不是权限系统
  • 鉴权 = 看 Token 里的 role,不看请求从哪来
  • 同源方案 = 一个进程 / 反向代理 / 静态托管,三条路
  • 改端口 = 只换坐标值,凑不出同源
  • 浏览器一直在做同源判定,你感知不到只是因为结果一直是通过

附:本次讨论解决的一个真实困惑

"我用 Spring Boot 3,页面从 8080 打开,测试阶段从来没遇到跨域。"

这不是运气,是部署形态本来就不跨域。同源时浏览器判定为通过,CORS 机制静默存在但不触发。

只有在引入 Vite dev server、把前端放到 CDN、或前后端拆成两个容器时,它才会找上门。

相关推荐
锋行天下4 小时前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan5 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
计算机魔术师5 小时前
Anthropic官方插件被「寄生」,背后是同一类攻击又来了
前端
我命由我123455 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
key_3_feng6 小时前
评估与对齐:Agent 自我进化的护栏
前端·数据库·agent
LaughingZhu6 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
Lank_M7 小时前
抠图结果缩放后边缘发脏,该怪的不是插值
前端
jerrywus7 小时前
Skill、MCP、Workflow、Rules、Memory 有什么区别?拿几次真实开发任务讲清楚
前端·agent·claude
补码缩写7 小时前
canvas 与 sharp 缩放透明图:默认预乘,非预乘得拆通道
前端