1. 跨域问题的本质与Nginx解决方案
前端开发中最让人头疼的问题之一就是跨域限制。当你的前端应用运行在http://localhost:8080,而API服务部署在http://api.example.com时,浏览器会直接阻止这种跨域请求。这不是bug,而是浏览器出于安全考虑设计的同源策略(Same-Origin Policy)。
同源策略要求协议、域名和端口三者完全相同才允许直接通信。现代前后端分离架构中,前端静态资源与后端API服务往往部署在不同域名下,这就产生了跨域问题。常见的错误提示是:
Access to XMLHttpRequest at 'http://api.example.com/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Nginx作为高性能的反向代理服务器,可以通过配置CORS(跨域资源共享)规则来解决这个问题。相比在前端使用JSONP等方案,Nginx方案有以下优势:
- 不依赖前端代码修改
- 支持所有HTTP方法
- 可以精细控制允许的域名、方法和头部
- 性能开销几乎可以忽略
2. Nginx基础CORS配置
最简单的CORS配置是在Nginx的server或location块中添加以下指令:
add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type";这个配置表示:
- 允许所有域名访问(
*) - 允许GET、POST和OPTIONS方法
- 允许Content-Type请求头
注意:生产环境不建议使用
*作为允许的域名,这会导致安全风险。应该明确指定允许的域名。
3. 生产环境推荐配置
对于生产环境,我们需要更精细的控制。下面是一个完整的配置示例:
server { listen 80; server_name api.example.com; # 根据请求来源动态设置允许的域名 set $cors_origin ""; if ($http_origin ~* "^https://www.example.com$") { set $cors_origin $http_origin; } if ($http_origin ~* "^https://staging.example.com$") { set $cors_origin $http_origin; } location / { # 处理预检请求(OPTIONS) if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $cors_origin; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With"; add_header Access-Control-Max-Age 1728000; # 预检请求缓存20天 add_header Content-Type "text/plain; charset=UTF-8"; add_header Content-Length 0; return 204; } # 处理实际请求 add_header Access-Control-Allow-Origin $cors_origin; add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Expose-Headers "Content-Length, X-Total-Count"; # 你的代理配置 proxy_pass http://backend; } }这个配置实现了:
- 动态允许特定域名(www和staging环境)
- 正确处理OPTIONS预检请求
- 允许携带凭证(cookie等)
- 暴露自定义响应头
- 预检请求结果缓存20天
4. 常见问题与解决方案
4.1 配置不生效的可能原因
- 缓存问题:浏览器可能会缓存CORS响应,修改配置后记得清除缓存
- 配置位置错误:add_header必须放在server或location块中,不能放在http块
- 多个add_header冲突:同一指令的多个add_header只有最后一个会生效
- Nginx reload未生效:修改配置后需要执行
nginx -s reload
4.2 特殊场景处理
带凭证的请求:当请求需要携带cookie时,需要特殊处理:
add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Allow-Origin $http_origin; # 不能是* add_header Access-Control-Allow-Headers "Cookie, Set-Cookie";WebSocket跨域:WebSocket不受同源策略限制,但某些浏览器会检查Origin头:
location /socket { proxy_pass http://websocket_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Origin $http_origin; }4.3 性能优化建议
- 合理设置
Access-Control-Max-Age减少预检请求 - 避免使用
*作为允许的域名 - 合并多个add_header为一个,减少配置复杂度
- 对于静态资源,可以在Nginx直接设置CORS头,避免经过后端应用
5. 安全注意事项
- 不要过度放宽权限:只开放必要的HTTP方法和头部
- 验证Origin头:防止伪造Origin头的攻击
- 限制暴露的头部:
Access-Control-Expose-Headers只暴露必要的头部 - HTTPS强制:生产环境应该只允许HTTPS跨域
- 监控异常请求:关注异常的OPTIONS请求,可能是攻击探测
6. 实际案例:前后端分离项目配置
假设我们有一个Vue前端项目(部署在https://front.example.com)和一个Node.js后端API(部署在https://api.example.com),完整的Nginx配置如下:
前端Nginx配置:
server { listen 443 ssl; server_name front.example.com; root /var/www/front/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }后端API Nginx配置:
server { listen 443 ssl; server_name api.example.com; location / { if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "https://front.example.com"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization, Content-Type"; add_header Access-Control-Max-Age 86400; return 204; } add_header Access-Control-Allow-Origin "https://front.example.com"; add_header Access-Control-Allow-Credentials true; proxy_pass http://node_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置确保了:
- 前端部署在HTTPS环境
- 后端只接受来自前端域名的请求
- 支持常见的REST方法
- 允许授权和内容类型头
- 预检请求缓存1天
- 支持带凭证的请求
7. 调试技巧
当CORS配置不工作时,可以按以下步骤排查:
- 检查浏览器控制台错误信息
- 使用curl模拟请求:
curl -I -X OPTIONS -H "Origin: https://front.example.com" https://api.example.com/resource - 检查Nginx日志:
tail -f /var/log/nginx/access.log - 确认Nginx配置已重载:
nginx -t && nginx -s reload - 使用浏览器开发者工具查看请求和响应头
8. 进阶配置:多环境支持
对于需要支持多个环境(开发、测试、生产)的场景,可以使用Nginx的map指令:
map $http_origin $allowed_origin { default ""; "~^https://dev.example.com" $http_origin; "~^https://staging.example.com" $http_origin; "~^https://prod.example.com" $http_origin; } server { ... location / { if ($allowed_origin = "") { return 403; } if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $allowed_origin; ... return 204; } add_header Access-Control-Allow-Origin $allowed_origin; ... } }这种配置方式更易于维护,可以集中管理允许的域名列表。