window.postMessage();JSONP、document.domain 等旧方案只适合特定历史项目。本文面向前端初学者和需要排查接口跨域的开发者,讲清原理、代码与选型方法。
核心要点
- 同源要求协议、主机名、端口三项一致,路径不同不影响同源。
- 跨域是浏览器执行的安全机制,不等于服务器没有收到请求。
- 普通接口优先用 CORS;希望浏览器只看到同源地址时,使用反向代理。
- 携带 Cookie 时,CORS 不能使用
Access-Control-Allow-Origin: *。- 不推荐在新项目中使用
document.domain、JSONP、window.name + iframe或location.hash + iframe。
什么是跨域?先理解同源策略
当一个页面的源(origin)与它要读取资源的源不一致时,就产生了跨域场景。源由协议、主机名和端口共同决定,而不是只看域名。两个不同域名即使解析到同一个 IP 地址,也仍然不同源。
假设当前页面是 https://www.example.com/app/index.html:
| 请求地址 | 是否同源 | 原因 |
|---|---|---|
https://www.example.com/api/user |
是 | 协议、主机名、默认端口均相同 |
http://www.example.com/api/user |
否 | HTTPS 与 HTTP 协议不同 |
https://api.example.com/api/user |
否 | 主机名不同 |
https://www.example.com:8443/api/user |
否 | 端口不同 |
MDN 对同源策略的说明指出,同源策略会限制一个源如何与另一个源的资源交互。它是一道重要的浏览器安全边界,但不能替代完整的 XSS、CSRF 防护。
常见限制包括:脚本不能随意读取跨源页面的 DOM;不能任意读取跨源的 Cookie、localStorage 或 IndexedDB;跨源 fetch/XHR 的响应需要满足 CORS 等授权条件。需要注意,“允许嵌入资源”不等于“允许读取数据”:图片、脚本等资源可能被加载,不代表页面可以无限制读取其内容。
浏览器页面 → 发出跨源请求 → 服务器处理并响应
↓
浏览器检查 CORS 等授权信息
↓
允许 JS 读取,或拦截响应
图 1:跨域报错可能发生在响应返回之后,不能仅凭 Network 中的状态码判断。
9 种跨域解决方案对比
| 方案 | 典型场景 | 优点 | 主要限制 | 新项目建议 |
|---|---|---|---|---|
| CORS | 前后端分离、开放 API | 标准化,前端改动小 | 需要服务端正确配置 | 优先 |
| Nginx 反向代理 | 生产环境统一入口 | 对浏览器表现为同源,便于集中管理 | 需要部署和运维权限 | 优先 |
| Node.js 开发代理 | 本地开发、Vite/Webpack 项目 | 配置简单,不改动后端 | 通常只解决开发环境 | 推荐 |
window.postMessage() |
新窗口、父页面与 iframe 通信 | 原生支持双向消息 | 必须校验来源和消息格式 | 推荐 |
| JSONP | 只需 GET 的旧系统 | 实现简单,兼容旧浏览器 | 仅限 GET,安全和维护成本高 | 谨慎 |
| WebSocket | 实时消息、长连接 | 支持双向实时通信 | 不是普通 HTTP API 的通用替代品 | 按业务使用 |
document.domain |
同主域的历史子域页面 | 旧项目可能已有实现 | 已弃用,削弱源隔离 | 不推荐 |
window.name + iframe |
历史页面数据传递 | 可绕过部分旧限制 | 流程复杂,安全和维护困难 | 不推荐 |
location.hash + iframe |
历史 iframe 通信 | 不依赖现代消息 API | 能力有限,容易产生安全问题 | 不推荐 |
方案一:CORS 跨域资源共享
CORS(Cross-Origin Resource Sharing,跨源资源共享)通过 HTTP 响应头,让服务器声明哪些源可以读取响应。它是跨源接口请求的标准方案,关键配置在服务端,不是在前端请求里随意加一个同名请求头。MDN 的 CORS 指南还区分了简单请求、预检请求和携带凭据的请求。
1. 普通请求的服务端配置
下面以 Express 为例,只允许一个明确的前端源:
const express = require('express');
const app = express();
const allowedOrigin = 'https://www.example.com';
app.use((req, res, next) => {
res.set({
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Vary': 'Origin'
});
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
Access-Control-Allow-Origin 是最关键的响应头。只开放公共数据时可以使用 *,但涉及用户身份或敏感数据时,应采用明确来源白名单,不要直接反射任意 Origin。
2. 为什么浏览器先发送 OPTIONS?
当请求不满足“简单请求”条件时,浏览器通常会先发送 OPTIONS 预检请求,确认服务器允许相应方法和请求头,再决定是否发送实际请求。例如,使用 Authorization,或以 application/json 发送某些 POST 请求时,经常会看到预检。
因此,后端路由和网关必须正确处理 OPTIONS。只看到一次 OPTIONS 并不是接口被重复调用,而是浏览器在执行预检流程。
3. 携带 Cookie 的配置
前端需要主动开启凭据:
// fetch
fetch('https://api.example.com/user', {
credentials: 'include'
});
// axios
axios.get('https://api.example.com/user', {
withCredentials: true
});
服务端还需要返回:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
这里有两个高频错误:
- 携带凭据时不能使用
Access-Control-Allow-Origin: *,必须返回明确来源。 - CORS 放行不代表 Cookie 一定能发送,Cookie 仍受
SameSite、Secure和浏览器第三方 Cookie 策略约束。
方案二:Nginx 反向代理
如果页面和接口都通过 https://www.example.com 对外提供,浏览器看到的请求就是同源请求。Nginx 接收 /api/ 请求后,再将其转发到后端服务:
server {
listen 443 ssl;
server_name www.example.com;
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
前端只请求同源路径:
fetch('/api/user').then(response => response.json());
代理不是“让浏览器忽略同源策略”,而是改变了浏览器实际请求的入口。生产环境使用代理时,还要检查 HTTPS、路径映射、超时、请求体大小和真实客户端 IP 等配置。
方案三:Node.js 开发服务器代理
本地页面运行在 http://localhost:5173,接口运行在 http://localhost:3000 时,端口不同会导致跨域。以 Vite 为例,可在 vite.config.js 中配置:
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
};
开发服务器会代理 /api 请求,因此无需为了本地开发给测试接口开放任意来源。这份配置通常不会自动作用于生产构建,上线后仍应选择 CORS 或生产反向代理。
如果你还没有可调试的后端接口,可以先阅读站内的 Node.js 和 MySQL 增删查改 API 教程,完成基础接口搭建后再接入上述代理。
方案四:window.postMessage 跨窗口通信
postMessage 适用于页面与新窗口、父页面与 iframe 之间的数据传递,不是普通 AJAX 接口跨域的替代品。
发送方应指定精确的目标源:
const childWindow = window.open('https://child.example.com', 'child');
childWindow.postMessage(
{ type: 'USER_READY', payload: { id: 42 } },
'https://child.example.com'
);
接收方必须校验 origin,必要时同时校验 source 和数据结构:
window.addEventListener('message', (event) => {
if (event.origin !== 'https://parent.example.com') return;
if (event.source !== window.parent) return;
if (event.data?.type !== 'USER_READY') return;
console.log('收到可信消息:', event.data.payload);
});
MDN 对 postMessage 的安全说明强调:发送敏感消息时不应随意把 targetOrigin 设为 *,接收方也应验证发送者身份和消息语法,避免数据泄露或注入风险。
其他五种方案:JSONP、WebSocket 与旧式 iframe 方案
方案五:JSONP
JSONP 利用 <script> 标签加载跨源资源的能力,由服务器返回一段调用指定回调函数的 JavaScript:
<script>
function handleUser(data) {
console.log(data);
}
</script>
<script src="https://api.example.com/user?callback=handleUser"></script>
服务器返回的内容类似:
handleUser({ "id": 42, "name": "Alice" });
JSONP 通常只支持 GET,而且会把第三方响应作为脚本执行。只应连接可信端点,不要用它传输敏感数据,也不要把未经严格校验的回调名直接拼接进响应。
方案六:WebSocket
WebSocket 适用于聊天、实时行情和协同编辑等长连接场景。它不受 CORS 的同一套读取规则约束,但这不代表可以接受任意网站连接。服务端仍应校验 Origin,并完成身份认证和消息授权。不要用 WebSocket 重写普通 CRUD 接口,仅仅为了绕开 CORS。
方案七:document.domain
过去,同主域的不同子域页面会同时设置 document.domain = 'example.com',尝试放宽部分同源检查。但该方式会削弱浏览器提供的安全保护,MDN 已明确将 document.domain 标记为弃用,新项目不应采用。
方案八:window.name + iframe
该旧方案利用窗口名称在导航后仍可保留的特性传递数据,往往需要空白代理页和多次 iframe 导航。它实现复杂、调试困难,也容易引入不受信数据,建议迁移到 postMessage 或标准接口方案。
方案九:location.hash + iframe
该方案通过修改 iframe 的 URL 片段传递消息,曾用于旧浏览器中的跨窗口通信。它能传递的数据有限,还可能暴露于历史记录、日志或页面脚本。现代项目应优先使用带来源校验的 postMessage。
如何选择跨域解决方案
可以按下面的顺序判断:
- 前后端分离的正式接口:优先 CORS,服务端按环境配置来源白名单。
- 生产环境希望统一域名入口:使用 Nginx 或网关反向代理。
- 只有本地开发跨域:使用 Vite、Webpack 或 Node.js 开发服务器代理。
- iframe 或新窗口传消息:使用
postMessage,同时校验来源和数据。 - 实时双向业务:按需求使用 WebSocket,而不是为了规避 CORS。
- 维护历史系统:评估 JSONP 等旧方案的风险,并制定迁移计划。
简单说:API 选 CORS 或代理,窗口通信选 postMessage,实时通信选 WebSocket。
跨域报错排查清单
遇到跨域问题时,按以下顺序检查:
- 在控制台确认是 CORS、混合内容、Cookie,还是普通网络错误。
- 对比页面与接口的协议、主机名和端口,确认是否真正跨源。
- 在 Network 中检查实际请求的
Origin,不要只看前端配置了哪些请求头。 - 检查响应中的
Access-Control-Allow-Origin是否精确匹配页面源。 - 如果有预检,确认
OPTIONS返回成功,并允许实际方法和请求头。 - 携带 Cookie 时,确认前后端凭据配置正确,且未使用通配符
*。 - 检查网关、CDN 或后端是否重复、覆盖了 CORS 响应头。
- 使用代理时,确认生产环境也部署了对应规则,而非只有本地配置有效。
排查接口返回结构时,也可以使用站内的 JSON 格式化工具,先确认响应是否为合法 JSON,再继续检查 CORS 响应头。
常见问题 FAQ
跨域是前端问题还是后端问题?
跨域限制由浏览器执行,但解决方案通常需要前后端配合。CORS 主要由服务端响应头授权,代理则由开发服务器或部署网关实现。
接口返回 200,为什么前端仍提示跨域?
服务器可能已经处理并返回响应,但浏览器发现响应缺少有效 CORS 授权,于是阻止 JavaScript 读取。应同时检查控制台和响应头。
CORS 和反向代理有什么区别?
CORS 保留跨源请求,由服务器授权浏览器读取响应;反向代理让浏览器请求同源入口,再由服务器转发到后端。两者可按架构选用。
JSONP 可以解决 POST 跨域吗?
通常不能。JSONP 通过脚本标签发起 GET 请求,只适合特定旧式接口。需要 POST、PUT 或 DELETE 时,应使用 CORS 或服务器代理。
两个域名指向同一 IP,算同源吗?
不算。同源比较的是协议、主机名和端口,不比较服务器 IP。即使部署在同一台机器上,不同域名仍属于不同源。
结语
跨域并不可怕,关键是先识别业务场景,再选择正确的跨域解决方案:普通接口优先 CORS,统一入口使用反向代理,本地开发使用开发代理,iframe 通信使用安全的 postMessage。 不建议为了省事关闭浏览器安全策略,也不应在新项目中继续采用已经弃用或高风险的旧方案。
如果你正在整理自己的前端知识体系,可以继续访问 yfcov.com 的技术博客与实用工具,把本文的选型表和排查清单作为日常调试参考。
发布技术文章后,如果还想继续提升内容的关键词布局与自然搜索表现,可以参考站内的 Google SEO 完整指南:关键词、内容、外链与分析策略。