跨域(Cross-Origin)是浏览器出于安全考虑而实施的一种限制机制,用于阻止网页从一个 源(Origin) 向另一个源发起未经许可的资源请求或交互。

什么是跨域?

同源策略(Same-Origin Policy)

  • 源(Origin)的定义:由协议(Protocol)、**域名(Domain)端口(Port)**三部分组成。例如,https://www.example.com:443
  • 同源判断:两个 URL 的协议、域名、端口完全一致才属于同源。任意一项不同即构成跨域
    • ✅ 同源:https://a.comhttps://a.com/api
    • ❌ 跨域:https://a.comhttp://a.com(协议不同)、https://a.comhttps://b.com(域名不同)、https://a.com:80https://a.com:8080(端口不同)。
      跨域的限制范围
      浏览器会拦截以下跨域行为:
  • AJAX / Fetch 请求:默认禁止跨域请求(需服务器显式允许)。
  • DOM 访问:禁止通过 JavaScript 读取跨域页面的 DOM(如 iframe 内容)。
  • Cookie、LocalStorage:禁止跨域访问。

为什么要有跨域限制?

核心目的:安全性
跨域限制是浏览器同源策略的一部分,主要为了防止以下安全风险:

  1. CSRF(跨站请求伪造)
    恶意网站诱导用户发起跨域请求(如转账操作),盗用用户身份。
  2. XSS(跨站脚本攻击)
    攻击者窃取用户 Cookie 或敏感数据,通过跨域请求发送到自己的服务器。
  3. 数据泄露
    阻止网站 A 未经授权读取网站 B 的私有数据(如用户信息)。

试想一下:

  • 你登录银行网站 bank.com 后,访问恶意网站 evil.com
  • evil.com 的脚本可随意向 bank.com 发起请求,窃取账户数据或执行转账操作。
  • 仅仅只是看了一下余额你的钱就被转走了

JSONP

客户端代码(浏览器端)

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

// 2. 动态创建 script 标签并设置 src
const script = document.createElement("script");
script.src = "https://api.example.com/data?callback=handleResponse";
document.body.appendChild(script);

// 3. 脚本加载完成后自动执行 handleResponse(data)

服务器端代码(以 Node.js 为例)

1
2
3
4
5
app.get("/data", (req, res) => {
const data = { message: "Hello from server!" };
const callbackName = req.query.callback; // 获取客户端传递的回调函数名
res.send(`${callbackName}(${JSON.stringify(data)})`);
});

CORS(跨域资源共享)

现代浏览器推荐的跨域解决方案,需服务器设置响应头授权。

客户端代码(普通 AJAX)

1
2
3
4
5
6
fetch('https://api.example.com/data', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => console.log(data));

服务器端(Node.js + Express)

1
2
3
4
5
6
7
8
9
10
11
12
13
app.use((req, res, next) => {
// 允许所有域(生产环境应指定具体域名)
res.setHeader('Access-Control-Allow-Origin', '*');
// 允许的请求方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT');
// 允许的自定义头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});

app.get('/data', (req, res) => {
res.json({ message: 'CORS enabled!' });
});

代理服务器

通过同域服务器转发请求绕过浏览器限制。

前端代码

1
2
3
4
// 请求同域代理
fetch('/proxy/api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));

Node.js 代理服务器

1
2
3
4
5
6
7
8
9
10
11
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({});

http.createServer((req, res) => {
// 将 /proxy/xxx 转发到目标服务器
if (req.url.startsWith('/proxy')) {
const target = req.url.replace('/proxy/', 'https://');
proxy.web(req, res, { target });
}
}).listen(3000);

postMessage(跨窗口通信)

不同窗口/iframe 间安全通信的 API。

主页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<iframe id="iframe" src="https://other-domain.com"></iframe>
<script>
const iframe = document.getElementById('iframe');
iframe.onload = () => {
// 向 iframe 发送消息
iframe.contentWindow.postMessage('Hello!', 'https://other-domain.com');
};

// 接收来自 iframe 的消息
window.addEventListener('message', (event) => {
if (event.origin === 'https://other-domain.com') {
console.log('Received:', event.data);
}
});
</script>

iframe 内部

1
2
3
4
5
6
window.addEventListener('message', (event) => {
if (event.origin === 'https://main-domain.com') {
// 回复消息
event.source.postMessage('Hi back!', event.origin);
}
});

WebSocket

WebSocket 协议默认允许跨域通信。

客户端

1
2
3
4
5
6
7
8
9
const socket = new WebSocket('wss://api.example.com/socket');

socket.onopen = () => {
socket.send('Connected!');
};

socket.onmessage = (event) => {
console.log('Message:', event.data);
};

服务器端(Node.js + ws)

1
2
3
4
5
6
7
8
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
ws.on('message', (message) => {
ws.send('Echo: ' + message);
});
});

Nginx 反向代理

通过 Nginx 配置转发请求。

Nginx 配置

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name localhost;

location /api/ {
proxy_pass https://api.example.com/;
add_header Access-Control-Allow-Origin *;
}
}

各方案对比

方案 适用场景 优点 缺点
CORS 现代浏览器 API 调用 标准化、安全性高 需服务器支持
JSONP 老旧浏览器兼容性需求 兼容性好 仅 GET、安全性风险
代理 无法修改服务器头的场景 隐藏真实请求地址 需要额外服务器资源
postMessage 跨窗口/iframe 通信 安全可控 仅限窗口间通信
WebSocket 实时双向通信 原生跨域支持 协议复杂度高

根据实际需求选择最合适的跨域方案。