跨域处理方案
跨域问题的本质
跨域问题源于浏览器的同源策略限制。同源策略要求协议、域名和端口完全相同才能自由通信。不同源的请求会被浏览器拦截,导致前端无法获取响应数据。常见的跨域场景包括前后端分离开发、调用第三方API、子域名不同等。
JSONP方案
JSONP利用<script>
标签不受同源策略限制的特性实现跨域请求。客户端定义一个回调函数,服务器返回调用该函数的JavaScript代码。
// 客户端代码
function handleResponse(data) {
console.log('Received data:', data);
}
const script = document.createElement('script');
script.src = 'http://example.com/api?callback=handleResponse';
document.body.appendChild(script);
// 服务器响应
handleResponse({data: "some data"});
缺点:
- 仅支持GET请求
- 安全性较差
- 错误处理困难
CORS方案
CORS(跨域资源共享)是现代浏览器支持的标准化跨域解决方案。服务器通过设置响应头来声明允许的跨域请求。
简单请求
满足以下条件即为简单请求:
- 方法为GET/HEAD/POST
- Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded
// 服务器设置
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://client-domain.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
next();
});
预检请求
不满足简单请求条件的请求会先发送OPTIONS预检请求。
// 服务器处理预检请求
app.options('/api', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'http://client-domain.com');
res.setHeader('Access-Control-Allow-Methods', 'PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.status(204).end();
});
携带凭证的请求
需要额外设置Access-Control-Allow-Credentials
头:
// 客户端
fetch('http://api.example.com', {
credentials: 'include'
});
// 服务器
res.setHeader('Access-Control-Allow-Credentials', 'true');
代理服务器方案
通过同源的后端服务器转发请求,绕过浏览器限制。Node.js实现示例:
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
app.use('/api', createProxyMiddleware({
target: 'http://target-server.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}));
app.listen(3000);
WebSocket跨域
WebSocket协议不受同源策略限制,但服务器需要显式允许跨域连接:
// 客户端
const socket = new WebSocket('ws://server.example.com');
// Node.js服务器
const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 8080,
verifyClient: (info, done) => {
// 验证origin
done(info.origin === 'http://allowed-client.com');
}
});
postMessage跨域
适用于iframe/window之间的通信:
// 发送方
window.parent.postMessage('message content', 'http://target-origin.com');
// 接收方
window.addEventListener('message', (event) => {
if (event.origin !== 'http://trusted-origin.com') return;
console.log('Received message:', event.data);
});
Nginx反向代理
通过Nginx配置实现请求转发:
server {
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://backend-server/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
跨域资源共享的实际应用
处理复杂CORS场景
// Express中间件
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = ['https://domain1.com', 'https://domain2.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Custom-Header'],
credentials: true,
maxAge: 86400
};
app.use(cors(corsOptions));
处理预检请求缓存
app.options('*', cors(corsOptions)); // 全局预检请求处理
安全注意事项
- 严格限制
Access-Control-Allow-Origin
,避免使用*
- 对敏感操作实施CSRF防护
- 验证所有跨域请求的Origin头
- 限制允许的HTTP方法和头信息
// 安全示例
app.use((req, res, next) => {
const allowedOrigins = new Set([
'https://www.myapp.com',
'https://api.myapp.com'
]);
const origin = req.headers.origin;
if (allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
// 其他安全头
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
性能优化策略
- 预检请求缓存:设置
Access-Control-Max-Age
- 减少不必要的预检请求:尽量使用简单请求
- CDN加速跨域资源
- 压缩跨域响应数据
// 设置预检请求缓存
app.use((req, res, next) => {
res.setHeader('Access-Control-Max-Age', '600');
next();
});
常见问题排查
- 检查响应头是否正确设置
- 确认请求是否满足简单请求条件
- 验证服务器是否正确处理OPTIONS方法
- 检查浏览器控制台错误信息
- 使用curl测试API响应头
# 使用curl测试
curl -I -X OPTIONS http://api.example.com/resource
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:RESTful API设计
下一篇:负载均衡策略