浅谈跨域

跨域是前端开发中最常见的问题之一,其根源是浏览器的同源策略。下面将从「什么是跨域」「为什么会有跨域」「解决跨域的主流方案」三个维度详细解析,彻底搞懂跨域。

一、什么是跨域?

1. 同源策略(Same-Origin Policy)

跨域的本质是浏览器的同源安全策略:浏览器为了保护用户隐私和安全,禁止不同源的页面之间直接进行数据交互(如 AJAX 请求、DOM 操作、Cookie 读取等)。

同源的定义:两个页面的「协议」「域名」「端口」完全一致,即为同源,只要有一个不同,就是跨域。

2. 跨域的常见场景

举个例子,假设当前页面 URL 是 http://www.example.com:80(默认端口80可省略),以下情况均属于跨域:

目标 URL 跨域原因 说明
https://www.example.com 协议不同 http vs https
http://api.example.com 域名不同 主域名相同,子域名不同
http://www.another.com 域名不同 完全不同的域名
http://www.example.com:8080 端口不同 80 vs 8080

3. 跨域的影响

同源策略限制了以下行为:

  • ❌ 无法发送 AJAX/Fetch 请求到不同源的服务器;
  • ❌ 无法读取/操作不同源页面的 DOM(如 iframe 嵌套的页面);
  • ❌ 无法读取不同源的 Cookie、LocalStorage、IndexedDB 等存储数据;
  • ✅ 但允许资源加载(如 <img><script><link><video> 等标签的 src/href 不受同源限制)—— 这是 JSONP 等方案的基础。

二、为什么要有跨域?(同源策略的意义)

同源策略是浏览器的核心安全机制,主要为了防止恶意网站窃取用户数据

  • 假设你登录了银行网站 bank.com,Cookie 中保存了登录状态;
  • 若没有同源策略,恶意网站 evil.com 就能直接通过 AJAX 请求 bank.com 的接口,窃取你的账户信息、转账等;
  • 同源策略限制了这种跨域数据交互,保护了用户隐私。

三、解决跨域的主流方案(详细解析)

解决跨域的核心思路有两个:

  1. 绕过浏览器限制:利用浏览器的“漏洞”或特殊标签(如 JSONP、代理服务器);
  2. 服务器端配合:服务器明确告诉浏览器“允许跨域”(如 CORS)。

以下是最常用的 6 种方案,按推荐程度排序:

方案 1:CORS(Cross-Origin Resource Sharing,跨域资源共享)—— 最标准、最推荐

CORS 是 W3C 官方推荐的跨域解决方案,需要服务器端配合,通过设置 HTTP 响应头告诉浏览器“允许该域名跨域访问”。

原理

浏览器会自动判断请求是否跨域:

  • 对于简单请求(GET/POST/HEAD,无自定义头),浏览器直接发送请求,服务器返回 Access-Control-Allow-Origin 等响应头;
  • 对于非简单请求(如 PUT/DELETE、带自定义头),浏览器会先发送一个 OPTIONS 预检请求,确认服务器允许后再发送真实请求。
服务器端实现(以 Node.js/Express 为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const express = require('express');
const app = express();

// 配置 CORS 响应头
app.use((req, res, next) => {
// 1. 允许的源(* 表示允许所有,生产环境建议替换为具体域名)
res.setHeader('Access-Control-Allow-Origin', 'http://www.example.com');
// 2. 允许的请求方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 3. 允许的请求头(如 Content-Type、Authorization)
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 4. 允许携带 Cookie(需配合前端 withCredentials: true)
res.setHeader('Access-Control-Allow-Credentials', 'true');

// 处理 OPTIONS 预检请求(直接返回 200)
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});

// 测试接口
app.get('/api/data', (req, res) => {
res.json({ message: 'CORS 跨域成功!' });
});

app.listen(3000, () => console.log('服务器运行在 3000 端口'));
前端请求(Fetch 示例)
1
2
3
4
5
6
7
8
// 简单请求
fetch('http://localhost:3000/api/data', {
method: 'GET',
// 若需携带 Cookie,需设置 credentials: 'include'
credentials: 'include'
})
.then(res => res.json())
.then(data => console.log(data));
优缺点
  • ✅ 优点:标准、灵活、支持所有请求方法、可控制权限;
  • ❌ 缺点:需要服务器端配合,IE10 以下不支持(但现在基本不用考虑 IE 了)。

方案 2:代理服务器(Nginx / Vue/React DevServer)—— 开发环境最常用

同源策略是浏览器的限制,服务器之间没有跨域问题!因此可以通过「同域代理服务器」转发请求到目标服务器,绕过浏览器限制。

场景 1:开发环境(Vue/React DevServer 代理)

以 Vue CLI 为例,在 vue.config.js 中配置代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
devServer: {
proxy: {
// 匹配以 /api 开头的请求
'/api': {
target: 'http://localhost:3000', // 目标服务器地址
changeOrigin: true, // 改变请求头中的 Origin 为目标服务器
pathRewrite: {
'^/api': '' // 可选:重写路径(如 /api/data → /data)
}
}
}
}
};

前端请求时直接写 /api/data,DevServer 会自动转发到 http://localhost:3000/data,完美解决跨域。

场景 2:生产环境(Nginx 反向代理)

Nginx 作为前端服务器,将 API 请求转发到后端服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name www.example.com;

# 前端静态文件
location / {
root /usr/share/nginx/html;
index index.html;
}

# API 请求代理到后端
location /api {
proxy_pass http://backend-server:3000; # 后端服务器地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

此时前端和 Nginx 同域,Nginx 转发请求到后端,无跨域问题。

优缺点
  • ✅ 优点:无需修改后端代码、开发/生产环境通用、性能好;
  • ❌ 缺点:需要配置代理服务器(Nginx 或 DevServer)。

方案 3:JSONP(JSON with Padding)—— 已逐渐淘汰

JSONP 是一种“古老”的跨域方案,利用 <script> 标签不受同源限制的特点实现。

原理
  1. 前端动态创建一个 <script> 标签,src 指向目标接口,并通过 callback 参数传递回调函数名;
  2. 服务器接收请求后,将数据包裹在回调函数中返回(如 callback({ data: 'xxx' }));
  3. 浏览器加载 <script> 后,自动执行回调函数,拿到数据。
实现示例

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 定义回调函数
function handleResponse(data) {
console.log('拿到数据:', data);
}

// 2. 动态创建 script 标签
const script = document.createElement('script');
script.src = 'http://localhost:3000/api/jsonp?callback=handleResponse';
document.body.appendChild(script);

// 3. 请求完成后移除 script 标签
script.onload = () => document.body.removeChild(script);

服务器端代码(Node.js)

1
2
3
4
5
6
app.get('/api/jsonp', (req, res) => {
const callback = req.query.callback; // 获取回调函数名
const data = { message: 'JSONP 跨域成功!' };
// 返回:handleResponse({ message: '...' })
res.send(`${callback}(${JSON.stringify(data)})`);
});
优缺点
  • ✅ 优点:兼容性极好(支持老版本 IE)、实现简单;
  • ❌ 缺点:仅支持 GET 请求、不安全(容易 XSS 攻击)、需要服务器配合。
  • 现状:已逐渐被 CORS 替代,仅在特殊老项目中使用。

方案 4:document.domain —— 仅适用于「主域名相同,子域名不同」的场景

如果两个页面的主域名相同(如 a.example.comb.example.com),可以通过设置 document.domain 为相同的主域名,实现跨域 DOM 操作。

实现示例

页面 A(a.example.com

1
2
3
4
5
6
7
8
9
10
11
<iframe id="iframe" src="http://b.example.com"></iframe>
<script>
// 设置 document.domain 为主域名
document.domain = 'example.com';

// 等待 iframe 加载完成后操作其 DOM
document.getElementById('iframe').onload = () => {
const iframeDoc = document.getElementById('iframe').contentDocument;
console.log(iframeDoc.getElementById('target').textContent);
};
</script>

页面 B(b.example.com

1
2
3
4
5
<div id="target">我是页面 B 的内容</div>
<script>
// 必须和页面 A 设置相同的 document.domain
document.domain = 'example.com';
</script>
优缺点
  • ✅ 优点:实现简单、无需服务器配合;
  • ❌ 缺点:仅适用于主域名相同的场景、无法解决 AJAX 跨域、存在安全风险。

方案 5:window.postMessage —— HTML5 官方 API,用于「跨窗口通信」

postMessage 是 HTML5 提供的 API,专门用于不同窗口/iframe 之间的跨域通信(不管是否同源)。

原理
  • 发送方:通过 targetWindow.postMessage(data, targetOrigin) 发送消息;
  • 接收方:通过 window.addEventListener('message', (e) => { ... }) 接收消息。
实现示例

父窗口(http://www.example.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<iframe id="iframe" src="http://www.another.com"></iframe>
<script>
const iframe = document.getElementById('iframe');

// 等待 iframe 加载完成后发送消息
iframe.onload = () => {
// 发送消息到 iframe
iframe.contentWindow.postMessage(
{ message: '你好,我是父窗口!' }, // 数据
'http://www.another.com' // 目标源(* 表示任意,生产环境建议指定)
);
};

// 接收 iframe 的回复
window.addEventListener('message', (e) => {
// 验证来源(安全起见,必须验证!)
if (e.origin !== 'http://www.another.com') return;
console.log('收到 iframe 的回复:', e.data);
});
</script>

子窗口(http://www.another.com

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// 接收父窗口的消息
window.addEventListener('message', (e) => {
if (e.origin !== 'http://www.example.com') return;
console.log('收到父窗口的消息:', e.data);

// 回复父窗口
e.source.postMessage(
{ message: '你好,我是子窗口!' },
e.origin
);
});
</script>
优缺点
  • ✅ 优点:HTML5 标准、安全(可验证 origin)、支持任意窗口通信;
  • ❌ 缺点:仅适用于窗口/iframe 之间的通信,无法解决 AJAX 跨域。

方案 6:WebSocket —— 不受同源策略限制

WebSocket 是 HTML5 提供的全双工通信协议,本身不受同源策略限制,因此可以直接跨域通信。

简单示例
1
2
3
4
// 前端 WebSocket 连接(直接连接跨域服务器)
const ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => ws.send('Hello WebSocket!');
ws.onmessage = (e) => console.log('收到消息:', e.data);
优缺点
  • ✅ 优点:不受同源限制、全双工通信、性能好;
  • ❌ 缺点:需要服务器支持 WebSocket、仅适用于实时通信场景。

四、方案总结与推荐

方案 推荐指数 适用场景
CORS ⭐⭐⭐⭐⭐ 绝大多数生产环境场景
代理服务器(Nginx/DevServer) ⭐⭐⭐⭐⭐ 开发环境、生产环境通用
postMessage ⭐⭐⭐⭐ 跨窗口/iframe 通信
document.domain ⭐⭐⭐ 主域名相同的子域名跨域
JSONP ⭐⭐ 老项目兼容(已淘汰)
WebSocket ⭐⭐⭐⭐ 实时通信场景

最终建议

  • 开发环境:用 DevServer 代理(Vue/React 都支持);
  • 生产环境:优先用 CORS,若无法修改后端代码则用 Nginx 反向代理;
  • 跨窗口通信:用 postMessage。

浅谈跨域
https://cszy.top/2015-03-03 浅谈跨域/
作者
csorz
发布于
2015年3月3日
许可协议