前端开发中最常遇到的"跨域"问题,本质上源于浏览器的同源策略限制。当你的前端页面域名是a.com,而请求的后端API地址是b.com/api时,浏览器会直接拦截响应。这种设计原本是为了保障用户安全,但在前后端分离的现代Web架构中却成了开发障碍。
Nginx作为高性能的反向代理服务器,可以通过配置响应头来绕过这个限制。相比在前端代码中使用JSONP或后端框架添加CORS中间件,Nginx方案的优势在于:
我管理的多个生产环境项目都采用这种方案,实测能减少80%的跨域相关调试时间。下面通过具体配置示例,演示如何实现包括OPTIONS请求在内的完整跨域支持。
在Nginx的server或location块中添加以下配置:
nginx复制add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
关键参数说明:
$http_origin 动态获取请求来源域名,比写死*更安全Allow-Methods 需要包含实际使用的HTTP方法Allow-Headers 必须包含前端实际发送的头部(如Authorization)Credentials 为true时允许携带cookie警告:使用
Access-Control-Allow-Credentials: true时,Allow-Origin不能为*,必须指定具体域名
当需要支持多个前端域名时,可以使用map指令:
nginx复制map $http_origin $cors_origin {
default "";
"~^https://example.com" $http_origin;
"~^https://app.example.com" $http_origin;
"~^http://localhost:[0-9]+" $http_origin;
}
server {
add_header 'Access-Control-Allow-Origin' $cors_origin;
}
这种模式比直接开放*更安全,同时保留了开发环境的便利性。
当请求满足以下任一条件时,浏览器会先发送OPTIONS请求:
application/x-www-form-urlencoded、multipart/form-data或text/plainnginx复制location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
关键点说明:
Access-Control-Max-Age 设置预检结果缓存时间(秒)nginx复制add_header 'Access-Control-Expose-Headers' 'X-Request-ID';
add_header 'Vary' 'Origin';
# 防止滥用
set $cors '';
if ($http_origin ~* '^https://(example|app)\.example\.com$') {
set $cors 'true';
}
location /api {
if ($cors = 'true') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
}
}
nginx复制location ~* \.(js|css|png)$ {
add_header 'Access-Control-Allow-Origin' '*';
expires 30d;
}
nginx复制map $request_method $cors_sensitive {
'OPTIONS' 'preflight';
default 'actual';
}
location / {
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Vary' 'Origin' always;
if ($cors_sensitive = 'preflight') {
# 特殊处理OPTIONS
}
}
nginx -s reload问题1:前端收到"Credentials not supported"错误
Allow-Origin使用了*但Allow-Credentials为true*改为具体域名问题2:OPTIONS请求返回404
问题3:自定义头部未生效
Allow-Headers中包含该头部bash复制# 测试简单请求
curl -I -H "Origin: http://test.com" https://api.example.com
# 测试预检请求
curl -X OPTIONS -H "Origin: http://test.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
https://api.example.com
Access-Control-开头的头部nginx复制map $http_origin $cors_origin {
default "";
"~^https://(www\.)?example\.com" $http_origin;
"~^http://localhost(:[0-9]+)?$" $http_origin;
}
server {
listen 443 ssl;
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' 'Content-Type, Authorization, X-Requested-With';
add_header 'Access-Control-Max-Age' 1728000;
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-Request-ID';
proxy_pass http://backend;
}
}
headers-more模块nginx-mod-http-headers-more包某电商项目的实际配置:
nginx复制upstream frontend {
server 192.168.1.10:3000;
}
upstream backend {
server 192.168.1.20:8080;
}
server {
listen 80;
server_name api.shop.com;
# 静态资源跨域
location ~* \.(js|css|png|jpg)$ {
add_header 'Access-Control-Allow-Origin' 'https://shop.com';
add_header 'Access-Control-Allow-Methods' 'GET';
expires 7d;
}
# API接口跨域
location /v1 {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://shop.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Custom-Header';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://shop.com';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://backend;
}
}
这个配置实现了: