什么是跨域?
跨域(Cross-Origin)是指浏览器出于安全考虑,限制从一个源(Origin)加载或访问另一个源的资源或数据。一个“源”由协议、域名和端口号共同决定。例如:
http://example.com
和https://example.com
是不同的源(协议不同)。http://example.com
和http://sub.example.com
是不同的源(域名不同)。http://example.com:8080
和http://example.com
是不同的源(端口号不同)。
同源策略(Same-Origin Policy) 是浏览器的一种安全机制,它限制了从一个源加载的文档或脚本如何与另一个源的资源进行交互。跨域请求会触发同源策略,导致请求被阻止,除非服务器明确允许。
跨域问题的常见场景
- 前端与后端分离:前端项目和后端API部署在不同的域名或端口上。
- 第三方API调用:前端需要调用第三方提供的API服务。
- 静态资源与主站分离:静态资源(如图片、CSS、JS)托管在CDN上,与主站域名不同。
跨域解决方案全集
1. CORS(跨域资源共享)
原理:CORS是W3C标准,允许服务器声明哪些源站有权限访问其资源。浏览器在发起跨域请求时,会自动在请求头中添加Origin
字段,服务器通过响应头中的Access-Control-Allow-Origin
等字段来控制是否允许跨域。
实现方式:
-
服务器端配置:
- 设置响应头
Access-Control-Allow-Origin
为允许的源(或*
表示允许所有源)。 - 设置
Access-Control-Allow-Methods
指定允许的HTTP方法(如GET、POST)。 - 设置
Access-Control-Allow-Headers
指定允许的请求头。 - 设置
Access-Control-Allow-Credentials
为true
以支持跨域传递Cookie。
- 设置响应头
-
示例(Node.js/Express):
```javascript
const express = require('express');
const app = express();app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://example.com'); // 允许特定源
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带Cookie
next();
});app.get('/api/data', (req, res) => {
res.json({ message: 'This is CORS-enabled for only example.com.' });
});app.listen(3000);
```
优点:
- 标准化解决方案,适用于所有现代浏览器。
- 支持复杂请求(如带自定义头或认证信息的请求)。
缺点:
- 需要服务器端支持。
- 配置不当可能导致安全风险。
2. JSONP(JSON with Padding)
原理:JSONP利用<script>
标签可以跨域加载资源的特性,通过动态创建<script>
标签,请求服务器返回一段包含回调函数的JavaScript代码,浏览器执行该代码,从而实现跨域数据获取。
实现方式:
-
客户端:
- 动态创建
<script>
标签,设置src
为跨域API的URL,并在URL中传递回调函数名。
- 动态创建
-
服务器端:
- 接收回调函数名参数,将数据包装在回调函数中返回。
-
示例:
-
客户端代码:
```html```
-
服务器端返回:
handleResponse({ message: 'This is JSONP data.' });
-
优点:
- 简单易用,兼容性好,支持所有浏览器。
缺点:
- 仅支持GET请求。
- 存在XSS(跨站脚本攻击)风险。
3. 代理服务器
原理:在前端与后端之间设置一个代理服务器,前端请求代理服务器,代理服务器再转发请求到目标服务器,最后将响应返回给前端。由于代理服务器与前端同源,浏览器不会触发跨域限制。
实现方式:
-
开发环境:使用Webpack DevServer或Vite等工具的代理功能。
- Webpack DevServer配置示例:
module.exports = { devServer: { proxy: { '/api': { target: 'http://example.com', changeOrigin: true, pathRewrite: { '^/api': '' }, }, }, }, };
- Webpack DevServer配置示例:
-
生产环境:使用Nginx或Apache等服务器作为反向代理。
-
Nginx配置示例:
```nginx
server {
listen 80;
server_name mydomain.com;location /api/ {
proxypass http://example.com/;
proxyset_header Host $host;
}
}
```
-
优点:
- 无需修改后端代码。
- 支持所有类型的请求。
缺点:
- 需要额外的服务器资源。
- 配置复杂度较高。
4. WebSocket
原理:WebSocket是一种在单个TCP连接上进行全双工通信的协议,它不受同源策略限制,可以用于跨域通信。
实现方式:
-
客户端:使用WebSocket API建立连接。
```javascript
const socket = new WebSocket('ws://example.com/socket');socket.onopen = () => {
console.log('WebSocket connection established.');
socket.send('Hello Server!');
};socket.onmessage = (event) => {
console.log('Message from server:', event.data);
};
``` -
服务器端:使用WebSocket库(如Node.js的
ws
)处理连接。
优点:
- 实时性高,适用于聊天、通知等场景。
- 不受同源策略限制。
缺点:
- 仅适用于实时通信场景。
- 需要服务器端支持WebSocket协议。
5. postMessage
原理:postMessage
是HTML5提供的一种跨文档通信API,允许不同源的窗口(如iframe)之间安全地传递消息。
实现方式:
-
父页面:
const iframe = document.getElementById('myIframe'); iframe.contentWindow.postMessage('Hello iframe!', 'http://example.com');
-
iframe页面:
window.addEventListener('message', (event) => { if (event.origin !== 'http://parent-domain.com') return; console.log('Message from parent:', event.data); });
优点:
- 适用于iframe与父页面之间的通信。
- 安全性高,可以验证消息来源。
缺点:
- 仅适用于窗口间通信。
- 实现复杂度较高。
与推荐
| 解决方案 | 适用场景 | 优点 | 缺点 |
|----------------|------------------------------|--------------------------------|--------------------------------|
| CORS | 现代浏览器,需要复杂请求 | 标准化,安全性高 | 需要服务器端支持 |
| JSONP | 简单GET请求,兼容性要求高 | 兼容性好,实现简单 | 仅支持GET,存在XSS风险 |
| 代理服务器 | 开发环境或无法修改后端时 | 无需修改后端,支持所有请求 | 需要额外服务器资源 |
| WebSocket | 实时通信场景 | 实时性高,不受同源策略限制 | 仅适用于实时通信 |
| postMessage| iframe与父页面通信 | 安全性高,适用于窗口间通信 | 仅适用于窗口间通信 |
推荐:
- 优先使用CORS,因为它是标准化的解决方案,安全性高,适用于大多数场景。
- 在无法修改后端或开发环境中,可以使用代理服务器。
- 对于实时通信场景,选择WebSocket。
- 在iframe与父页面通信时,使用postMessage。
希望以上内容能帮助你全面理解跨域问题及其解决方案!