跨域
跨域(CORS):前端通信的安全与突破
一、同源策略与跨域问题
同源策略(Same-Origin Policy):
浏览器默认限制页面从一个源(协议、域名、端口三者相同)加载的资源访问另一个源的资源。
- 同源示例:
https://example.com/a.js
与https://example.com/b.js
- 跨域示例:
https://api.example.com/data
与https://www.example.com
跨域限制:
- AJAX 请求:被浏览器阻止(如
fetch
、XMLHttpRequest
)。 - Cookie、LocalStorage:无法跨域访问。
- DOM 操作:不同源的页面无法互相操作。
二、跨域解决方案
1. CORS(跨域资源共享)
原理:服务器通过响应头声明允许哪些源访问资源。
关键响应头:
Access-Control-Allow-Origin
:允许的源(如https://example.com
或*
)。Access-Control-Allow-Methods
:允许的 HTTP 方法(如GET, POST
)。Access-Control-Allow-Headers
:允许的请求头(如Content-Type
)。Access-Control-Allow-Credentials
:是否允许携带凭证(如 Cookie)。
服务器配置示例(Node.js):
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});
前端请求示例:
fetch('https://api.example.com/data', {
credentials: 'include' // 携带 Cookie
})
.then(response => response.json())
.catch(error => console.error('CORS error:', error));
2. JSONP(JSON with Padding)
原理:利用 <script>
标签不受同源策略限制的特性。
步骤:
- 前端创建回调函数(如
handleData
)。 - 请求 URL 中添加回调函数名(如
?callback=handleData
)。 - 服务器返回包裹在回调函数中的 JSON 数据(如
handleData({"name":"John"})
)。
示例:
<script>
function handleData(data) {
console.log('Received data:', data);
}
</script>
<script src="https://api.example.com/data?callback=handleData"></script>
局限性:
- 仅支持
GET
请求。 - 安全性较低(易受 XSS 攻击)。
- 错误处理困难。
3. 代理服务器
原理:在同源的服务器上设置代理,转发请求到目标服务器。
示例(Node.js 代理):
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 将 /api 请求代理到 https://api.example.com
app.use('/api', createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}));
app.listen(3000);
前端请求:
// 访问同源的代理路径
fetch('/api/data')
.then(response => response.json());
4. WebSocket(不受同源限制)
原理:WebSocket 协议(ws://
或 wss://
)不遵循同源策略。
示例:
const socket = new WebSocket('wss://api.example.com/socket');
socket.onmessage = (event) => {
console.log('Received message:', event.data);
};
5. postMessage(跨窗口通信)
原理:用于不同窗口(如 iframe
、弹出窗口)之间的通信。
发送方:
// 在 https://example.com 页面中
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('Hello from parent!', 'https://iframe.example.com');
接收方:
// 在 https://iframe.example.com 页面中
window.addEventListener('message', (event) => {
if (event.origin === 'https://example.com') {
console.log('Received message:', event.data);
}
});
三、CORS 高级配置
1. 预检请求(Preflight Request)
- 对于复杂请求(如
PUT
、DELETE
,自定义请求头),浏览器会先发送OPTIONS
请求确认权限。 - 服务器需正确响应
OPTIONS
请求:app.options('/api/data', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
res.setHeader('Access-Control-Allow-Methods', 'PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(200);
});
2. 携带凭证(Credentials)
- 若需发送 Cookie 或 HTTP 认证,需同时设置:
// 前端
fetch('https://api.example.com', {
credentials: 'include'
});
// 后端
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', 'https://example.com'); // 不能用 *
四、安全注意事项
-
CORS 安全配置:
- 避免使用
Access-Control-Allow-Origin: *
处理敏感资源。 - 精确控制允许的域名和请求头。
- 避免使用
-
JSONP 风险:
- 验证
callback
参数,防止 XSS 攻击。
// 服务器端验证 callback 参数
const callback = req.query.callback;
if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(callback)) {
throw new Error('Invalid callback name');
} - 验证
-
代理服务器安全:
- 限制代理的目标域名,避免成为通用代理。
- 验证客户端请求,防止被利用攻击第三方服务。
五、工具与框架配置
1. Express.js(Node.js)
const cors = require('cors');
// 允许所有源
app.use(cors());
// 仅允许特定源
app.use(cors({
origin: 'https://example.com',
credentials: true
}));
2. Nginx 代理配置
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass https://api.example.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 添加 CORS 头
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
}
}
六、总结:选择合适的跨域方案
场景 | 方案 | 优点 | 缺点 |
---|---|---|---|
现代浏览器,API 支持 | CORS | 标准方法,支持所有 HTTP 方法 | 需要服务器配合 |
兼容性要求高,仅 GET 请求 | JSONP | 兼容性好 | 仅支持 GET,安全性低 |
前后端同源部署困难 | 代理服务器 | 完全可控,安全性高 | 需要额外服务器资源 |
实时通信 | WebSocket | 双向通信,无同源限制 | 需服务器支持 WebSocket |
跨窗口通信 | postMessage | 安全可靠 | 仅适用于窗口间通信 |
在实际开发中,优先考虑 CORS 方案,若服务器无法修改,再考虑代理服务器或其他替代方案。