跨域(Cross-Origin)是浏览器出于安全考虑而实施的一种限制机制,用于阻止网页从一个 源(Origin) 向另一个源发起未经许可的资源请求或交互。
什么是跨域?
同源策略(Same-Origin Policy)
- 源(Origin)的定义:由协议(Protocol)、**域名(Domain)和端口(Port)**三部分组成。例如,
https://www.example.com:443
。 - 同源判断:两个 URL 的协议、域名、端口完全一致才属于同源。任意一项不同即构成跨域。
- ✅ 同源:
https://a.com
和https://a.com/api
- ❌ 跨域:
https://a.com
和http://a.com
(协议不同)、https://a.com
和https://b.com
(域名不同)、https://a.com:80
和https://a.com:8080
(端口不同)。
跨域的限制范围
浏览器会拦截以下跨域行为:
- ✅ 同源:
- AJAX / Fetch 请求:默认禁止跨域请求(需服务器显式允许)。
- DOM 访问:禁止通过 JavaScript 读取跨域页面的 DOM(如
iframe
内容)。 - Cookie、LocalStorage:禁止跨域访问。
为什么要有跨域限制?
核心目的:安全性
跨域限制是浏览器同源策略的一部分,主要为了防止以下安全风险:
- CSRF(跨站请求伪造)
恶意网站诱导用户发起跨域请求(如转账操作),盗用用户身份。 - XSS(跨站脚本攻击)
攻击者窃取用户 Cookie 或敏感数据,通过跨域请求发送到自己的服务器。 - 数据泄露
阻止网站 A 未经授权读取网站 B 的私有数据(如用户信息)。
试想一下:
- 你登录银行网站
bank.com
后,访问恶意网站evil.com
。 evil.com
的脚本可随意向bank.com
发起请求,窃取账户数据或执行转账操作。- 仅仅只是看了一下余额你的钱就被转走了
JSONP
客户端代码(浏览器端)
// 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 为例)
app.get("/data", (req, res) => {
const data = { message: "Hello from server!" };
const callbackName = req.query.callback; // 获取客户端传递的回调函数名
res.send(`${callbackName}(${JSON.stringify(data)})`);
});
CORS(跨域资源共享)
现代浏览器推荐的跨域解决方案,需服务器设置响应头授权。
客户端代码(普通 AJAX)
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)
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!' });
});
代理服务器
通过同域服务器转发请求绕过浏览器限制。
前端代码
// 请求同域代理
fetch('/proxy/api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
Node.js 代理服务器
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。
主页面
<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 内部
window.addEventListener('message', (event) => {
if (event.origin === 'https://main-domain.com') {
// 回复消息
event.source.postMessage('Hi back!', event.origin);
}
});
WebSocket
WebSocket 协议默认允许跨域通信。
客户端
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)
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 配置
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 | 实时双向通信 | 原生跨域支持 | 协议复杂度高 |
根据实际需求选择最合适的跨域方案。