1. 问题背景与核心挑战
最近在做一个前后端分离项目时遇到一个典型场景:前端页面部署在公有云CDN上(域名A),需要调用部署在内网环境的API服务(域名B)。浏览器直接报错:
Access to XMLHttpRequest at 'http://internal-api/endpoint' from origin 'http://public-site.com' has been blocked by CORS policy这个跨域问题看似简单,但涉及多个技术层面的考量:
- 安全策略:浏览器同源策略(Same-Origin Policy)的合理限制
- 网络架构:公网与内网服务的通信链路设计
- 运维成本:解决方案的长期可维护性
2. 跨域问题本质解析
2.1 浏览器安全机制
现代浏览器会拦截跨域请求的响应,除非服务端明确声明允许该来源。这通过CORS(Cross-Origin Resource Sharing)机制实现,关键点在于:
- 简单请求(GET/POST/HEAD)直接发送,依赖响应头判断
- 复杂请求(如PUT/DELETE)会先发OPTIONS预检请求
2.2 典型错误场景分析
| 错误类型 | 触发条件 | 解决方案方向 |
|---|---|---|
| 缺少CORS头 | 服务端未返回Access-Control-Allow-Origin | 服务端配置 |
| 凭证问题 | 带cookie的请求未设置withCredentials | 客户端+服务端协同配置 |
| 方法不允许 | 预检请求未通过(如PUT方法未声明) | 服务端OPTIONS响应配置 |
3. 实战解决方案
3.1 方案一:Nginx反向代理(推荐)
这是最稳妥的解决方案,架构如下:
公网用户 → CDN静态资源 → Nginx代理 → 内网API具体配置示例:
server { listen 443 ssl; server_name api.public-site.com; location / { proxy_pass http://internal-api-server; # 关键CORS配置 add_header 'Access-Control-Allow-Origin' 'https://public-site.com'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,Content-Type'; add_header 'Access-Control-Allow-Credentials' 'true'; if ($request_method = 'OPTIONS') { return 204; } } }3.2 方案二:服务端直接支持CORS
以Node.js为例的Express配置:
app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://public-site.com'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('Access-Control-Allow-Credentials', 'true'); next(); });3.3 方案三:JSONP(仅限GET请求)
适用于老旧系统兼容:
<script> function handleResponse(data) { console.log('Received:', data); } </script> <script src="http://internal-api/data?callback=handleResponse"></script>4. 深度配置指南
4.1 安全加固建议
- 严格限制允许的源:避免使用通配符
* - 设置Vary头防止缓存污染:
add_header 'Vary' 'Origin'; - 对敏感接口添加CSRF保护
4.2 性能优化
- 预检请求缓存:
add_header 'Access-Control-Max-Age' 86400; - 合并小文件减少预检请求次数
5. 常见问题排查
5.1 问题现象:预检请求失败
排查步骤:
- 检查Network面板OPTIONS请求状态码
- 确认服务端正确响应OPTIONS方法
- 验证响应头包含
Access-Control-Allow-Methods
5.2 问题现象:带Cookie请求被拒绝
解决方案:
- 客户端设置:
fetch(url, { credentials: 'include' }); - 服务端配置:
add_header 'Access-Control-Allow-Credentials' 'true';
5.3 问题现象:响应头缺失
使用curl测试:
curl -I -X OPTIONS -H "Origin: https://public-site.com" https://api.example.com6. 进阶场景处理
6.1 多域名动态适配
Nginx map模块实现:
map $http_origin $allow_origin { default ""; "~^https://(site1|site2)\.com$" $http_origin; } server { add_header 'Access-Control-Allow-Origin' $allow_origin; }6.2 WebSocket跨域
需单独配置:
location /socket { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }7. 方案选型对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Nginx代理 | 生产环境首选 | 性能好,配置灵活 | 需要运维知识 |
| 服务端CORS | 全控制环境 | 直接精确控制 | 需修改应用代码 |
| JSONP | 老旧系统兼容 | 浏览器兼容性好 | 仅支持GET,安全性低 |
实际项目中,我通常会采用Nginx方案作为主要手段,配合服务端的精细控制。特别是在微服务架构下,API网关统一处理CORS问题是最佳实践。