跨域(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 2 3 4 5 6 7 8 9 10 11
| function handleResponse(data) { console.log("Received data:", data); }
const script = document.createElement("script"); script.src = "https://api.example.com/data?callback=handleResponse"; document.body.appendChild(script);
|
服务器端代码(以 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) => { 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.contentWindow.postMessage('Hello!', 'https://other-domain.com'); };
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 |
实时双向通信 |
原生跨域支持 |
协议复杂度高 |
根据实际需求选择最合适的跨域方案。