前端开发中最让人头疼的问题之一就是跨域。当你的前端页面尝试从不同域名、端口或协议的后端获取资源时,浏览器会直接拦截这些请求。这不是后端服务没收到请求,而是浏览器出于安全考虑主动拦截了响应。
跨域问题的核心在于浏览器的同源策略(Same-Origin Policy)。这个策略要求:
任何一项不满足,就会触发跨域限制。而Nginx作为高性能的反向代理服务器,可以通过配置响应头来优雅地解决这个问题,比在前端用JSONP或后端改代码要干净得多。
要让Nginx支持跨域,主要依靠这几个响应头:
nginx复制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';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
配置说明:
Allow-Origin: 指定允许访问的源,*表示允许任何域名Allow-Methods: 允许的HTTP方法Allow-Headers: 允许的请求头字段Expose-Headers: 允许前端访问的响应头警告:生产环境慎用
*通配符,应该明确指定允许的域名,如:
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com';
nginx复制location /api/ {
if ($request_method = 'OPTIONS') {
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';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
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';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
proxy_pass http://backend;
}
当请求满足以下任一条件时,浏览器会先发送OPTIONS预检请求:
application/x-www-form-urlencoded、multipart/form-data或text/plainNginx需要正确响应这些OPTIONS请求,否则后续的实际请求会被浏览器拦截。
nginx复制if ($request_method = 'OPTIONS') {
# 预检请求缓存时间
add_header 'Access-Control-Max-Age' 1728000;
# 避免OPTIONS请求被传递到后端
add_header 'Content-Length' 0;
return 204;
}
关键点:
204 No Content是OPTIONS请求的标准响应Access-Control-Max-Age减少重复预检请求硬编码允许的域名不够灵活,可以通过变量实现动态控制:
nginx复制map $http_origin $cors_origin {
default "";
"~^https://(.+\.)?example\.com$" $http_origin;
"~^https://(.+\.)?api\.example\.com$" $http_origin;
}
server {
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
# ...其他OPTIONS头
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin;
# ...其他CORS头
}
}
当请求需要携带Cookie等凭证信息时,需要特殊配置:
nginx复制add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' $http_origin;
注意:
Allow-Origin不能为*,必须明确指定域名withCredentials: trueAccess-Control-Max-Age减少预检请求nginx -s reloadAllow-Headers使用curl测试OPTIONS请求:
bash复制curl -X OPTIONS -H "Origin: http://test.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: X-Custom-Header" \
-I http://yournginx.com/api/
检查响应头是否包含正确的CORS头信息。
*作为Allow-Origin假设我们有一个Vue前端(https://front.example.com)和SpringBoot后端(https://api.example.com),完整配置如下:
nginx复制# api.example.com的Nginx配置
server {
listen 443 ssl;
server_name api.example.com;
# SSL配置省略...
set $cors_origin "";
if ($http_origin ~* "^https://front\.example\.com$") {
set $cors_origin $http_origin;
}
location / {
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';
add_header 'Access-Control-Max-Age' 86400;
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' 'X-Total-Count';
proxy_pass http://springboot_backend;
# 其他代理配置...
}
}
关键点:
虽然Nginx是解决跨域的主流方案,但了解其他方法也很重要:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Nginx配置 | 性能好,无需改代码 | 需要运维知识 | 前后端分离项目 |
| 后端代码实现 | 更灵活 | 增加后端复杂度 | 需要精细控制的API |
| JSONP | 兼容老浏览器 | 只支持GET | 传统项目兼容 |
| WebSocket | 无跨域限制 | 协议不同 | 实时应用 |
| CORS中间件 | 开发友好 | 性能开销 | Node.js/Python等 |
对于现代web应用,Nginx方案在大多数情况下都是最佳选择,特别是当:
通过适当增大Access-Control-Max-Age减少OPTIONS请求频率:
nginx复制add_header 'Access-Control-Max-Age' 86400; # 24小时
但要注意:
在Nginx日志中添加$http_origin记录:
nginx复制log_format cors_log '$remote_addr - $http_origin - [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
server {
access_log /var/log/nginx/cors.log cors_log;
}
这样可以:
使用工具模拟跨域请求压力测试:
bash复制# 安装vegeta
go get -u github.com/tsenart/vegeta
# 测试跨域请求
echo "GET https://api.example.com/resource" | \
vegeta attack -header "Origin: https://front.example.com" -duration=30s | \
vegeta report
重点关注:
只开放必要的方法:
nginx复制add_header 'Access-Control-Allow-Methods' 'GET, POST';
防止伪造Origin头:
nginx复制if ($http_origin !~* "^https://(.+\.)?example\.com$") {
return 403;
}
对敏感API禁用跨域:
nginx复制location /admin/ {
add_header 'Access-Control-Allow-Origin' 'none';
deny all;
}
增强整体安全性:
nginx复制add_header 'X-Frame-Options' 'SAMEORIGIN';
add_header 'X-Content-Type-Options' 'nosniff';
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
随着技术的发展,跨域处理也在演进:
但Nginx方案因其高性能和灵活性,仍然是生产环境的首选。我在实际项目中发现,合理的Nginx配置可以:
最后分享一个实用技巧:当遇到复杂的跨域问题时,可以先用Chrome开发者工具的Network面板查看:
这能帮你快速定位是配置问题还是代码问题。记住,跨域本质是浏览器行为,理解浏览器的工作机制是关键。