跨域问题
一、跨域本质与同源策略
跨域触发条件:当请求的 URL 与当前页面的协议、域名、端口任一不同时。
同源策略限制范围:
- AJAX/Fetch 请求
- Web 字体加载
- Web Workers
- Canvas 图像操作
- Cookie/LocalStorage 访问
浏览器控制台报错:Access-Control-Allow-Origin
header is present on the requested resource
二、9 大跨域解决方案
1. CORS (跨域资源共享) - 主流方案
// 服务端设置响应头
res.setHeader('Access-Control-Allow-Origin', 'https://client.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'); // 允许携带cookie
// 预检请求处理
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
CORS 类型:
- 简单请求:GET/POST/HEAD + 标准头
- 预检请求:需先发 OPTIONS 请求验证
2. JSONP - 传统方案
<!-- 前端 -->
<script>
function handleResponse(data) {
console.log('Received:', data);
}
</script>
<script src="https://api.com/data?callback=handleResponse"></script>
<!-- 后端响应 -->
handleResponse({"name": "John", "age": 30});
限制:仅 GET 请求,无错误处理,XSS 风险
3. Nginx 反向代理
server {
listen 80;
server_name client.com;
location /api/ {
proxy_pass http://api-server.com; # 代理到目标服务器
proxy_set_header Host $host;
add_header 'Access-Control-Allow-Origin' '*';
}
}
4. WebSocket - 双向通信
const socket = new WebSocket('wss://api.com');
socket.onopen = () => {
socket.send(JSON.stringify({ action: 'subscribe' }));
};
socket.onmessage = (event) => {
console.log('Data:', JSON.parse(event.data));
};
5. postMessage - 跨窗口通信
// 父窗口 (https://parent.com)
iframe.contentWindow.postMessage('Hello', 'https://child.com');
// 子窗口 (https://child.com)
window.addEventListener('message', (event) => {
if (event.origin !== 'https://parent.com') return;
console.log('Received:', event.data);
});
6. 代理服务器 (Node.js)
const axios = require('axios');
app.get('/proxy', async (req, res) => {
const response = await axios.get('https://api.com/data', {
params: req.query,
});
res.json(response.data);
});
7. document.domain - 子域跨域
// parent.parent.com
document.domain = 'parent.com';
// child.parent.com
document.domain = 'parent.com'; // 现在可以互相访问
8. 浏览器禁用安全策略 - 仅开发环境
# Chrome启动参数
chrome.exe --disable-web-security --user-data-dir="C:\temp"
9. CDN 代理 + CORS
<script src="https://cdn.com/cors-proxy?url=https://api.com/data"></script>
三、方案对比与选型
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
CORS | 主流 API 交互 | 标准化、安全可控 | 需服务端配合 |
Nginx 代理 | 生产环境部署 | 高性能、配置灵活 | 运维复杂度增加 |
JSONP | 老旧浏览器兼容 | 兼容性好 | 仅 GET、安全性低 |
WebSocket | 实时双向通信 | 全双工、高效 | 协议升级复杂 |
postMessage | 跨窗口/iframe 通信 | 安全隔离 | 仅窗口间通信 |
四、面试高频问题
CORS 预检请求何时触发?
- 使用非简单方法(PUT/DELETE)
- 包含自定义头(Authorization)
- Content-Type 非标准值
如何携带 Cookie 跨域?
// 前端 fetch(url, { credentials: 'include' }); // 服务端 res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Origin', 'https://exact.com'); // 不能为*
OPTIONS 请求是什么?
预检请求,检查服务器是否允许实际请求JSONP 为什么不安全?
- 无错误处理机制
- 可能执行恶意脚本
- 易受 CSRF 攻击
生产环境最佳实践?
- 主域相同用 CORS
- 不同域用 Nginx 代理
- 敏感操作加 CSRF Token
五、安全增强方案
CORS 白名单控制
const allowedOrigins = ['https://client.com', 'https://app.com']; if (allowedOrigins.includes(req.headers.origin)) { res.setHeader('Access-Control-Allow-Origin', req.headers.origin); }
CSRF Token 防护
// 服务端生成并下发 res.cookie('csrf_token', generateToken(), { httpOnly: true }); // 前端在请求头携带 fetch(url, { headers: { 'X-CSRF-Token': getCookie('csrf_token') }, });
CSP 增强防御
Content-Security-Policy: default-src 'self'; script-src trusted.com
六、面试回答策略
分层解析
"跨域本质是浏览器安全策略,解决核心在于:绕过限制(JSONP)或获得授权(CORS)。现代项目首选 CORS,需前后端协同实现"
场景化选型
"在电商项目中,主站和 API 网关同域用 CORS,第三方支付用 postMessage,实时通知用 WebSocket"
安全深度
"配置 CORS 时我们采用动态白名单+CSRF Token 双验证,避免使用通配符*,并对 OPTIONS 请求做速率限制"
性能优化
"通过 Nginx 代理将多个跨域请求合并,减少预检请求次数,预检结果缓存 24 小时(Access-Control-Max-Age)"
特殊场景
"跨域文件上传需注意:
- 预检请求包含
Content-Type: multipart/form-data
- 服务器处理 OPTIONS 时返回
Access-Control-Allow-Headers: content-type
"
- 预检请求包含
框架实践
// Express中间件 app.use( cors({ origin: config.allowedOrigins, methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], maxAge: 86400, }) );
💡 面试金句:
"跨域不是技术障碍而是安全特性。现代浏览器为 CORS 提供完备支持,配合服务端精细化的权限控制,既能保障安全又能实现灵活的资源共享。关键在于理解协议细节,避免一刀切的通配符配置。"