同源策略与 CORS:为什么本地调接口总报跨域?
知识背景
前端页面运行在浏览器里,而数据往往在另一个域名/端口的服务上。浏览器为了安全,默认只允许页面去访问与当前页面「同源」的资源;一旦协议、域名、主机名或端口三者有任意一项不同,就叫跨域。
CORS(Cross-Origin Resource Sharing) 是 W3C 约定的一套机制:由服务器通过响应头声明「允许哪些源、哪些方法、哪些头」来放行跨域读资源。它和 Node 也密切相关:很多「开发环境不跨域、上线跨域」的问题,本质是生产域名与接口域名不一致,而开发时用了代理把请求「同源化」了。
知识详解与通俗解释
1. 什么是「同源」?
两 URL 的 protocol(协议)、host(含域名)、port(端口) 完全一致才算同源。
例如页面是 https://app.example.com,接口是 https://api.example.com —— 不同源(子域不同)。
页面 http://localhost:5173,接口 http://localhost:3000 —— 不同源(端口不同)。
可以把它理解成:浏览器给每个「网站身份」发了一张门禁卡,默认只允许在自己小区里串门;要去别的小区,需要对方物业(服务器)给你登记放行。
2. 同源策略拦的是什么?
同源策略主要约束的是网页里的脚本通过浏览器发出的请求去读另一个源的响应内容(尤其是用 fetch / XHR)。注意区分:
- 你能发请求 ≠ 你能读到响应体。简单请求可能发出去,但若服务器没按 CORS 配好,浏览器会隐藏响应给 JS,控制台里看到典型报错:
blocked by CORS policy。 - 表单提交、
<img src>、<script src>等有自己的安全模型,和 XHR/fetch 的 CORS 规则不完全相同,不要混为一谈。
3. CORS 常见响应头(服务端在干什么)
Access-Control-Allow-Origin:允许的来源,可以是具体源(如https://app.example.com)或*(有 credential 时不能随意用*)。Access-Control-Allow-Methods:允许的 HTTP 方法,如GET, POST, PUT。Access-Control-Allow-Headers:允许前端带上的请求头(自定义头常需要这里列出来)。Access-Control-Allow-Credentials:是否允许携带 Cookie;为true时,Origin 一般不能是*,且前端要credentials: 'include'。
预检请求(preflight):当方法不是简单方法、或带了一些「非简单」头时,浏览器会先自动发一个 OPTIONS,问服务器「我待会儿要这样请求,行不行?」服务器用上面的头答复后,真正的请求才会发。
通俗说:浏览器先打电话问门卫能不能进,门卫说行,再正式进门。
4. 前端常见「解决思路」各自是什么
| 做法 | 本质 | 备注 |
|---|---|---|
| 后端正确配置 CORS | 规范做法 | 生产环境应以接口域名为准配置白名单 |
| 开发服务器代理(Vite/webpack devServer) | 浏览器只看到「同源」的 dev server,由它转发到后端 | 仅开发体验,不改变生产跨域事实 |
| 同源网关 / BFF | 页面与接口同一站点,由 Node/Nginx 再转发到内部服务 | 上线常用架构 |
| JSONP | 老方案,利用 <script> 不受同源读限制 | 仅 GET、安全风险大,新项目不推荐 |
Node 在 BFF 场景里常承担「对浏览器同源、对后端内网再转发」的角色,所以跨域问题经常是架构与部署问题,不是单纯前端一行代码能「关掉」的。
总结
- 同源由协议、主机、端口共同决定;跨域是常态,尤其是前后端分离与多子域部署。
- CORS 是服务器声明 + 浏览器强制执行;没配好就表现为「请求发了但 JS 拿不到数据」。
- 开发代理能缓解本地调试,上线仍要以 CORS 白名单或 BFF/同域反代为主。理解这一点,比死记「关掉浏览器安全」重要得多。