如何在 Nginx 中配置跨域资源共享(CORS)?
在 Nginx 中配置跨域资源共享(CORS),本质上是通过添加特定的 HTTP 响应头(Headers)来告诉浏览器允许跨域请求。
跨域请求通常分为两种:简单请求(如普通的 GET/POST)和预检请求(浏览器在发送复杂请求前,会先发送一个 OPTIONS 方法的请求进行探测)。因此,Nginx 的配置需要同时处理这两种情况。
以下是几种常见的 CORS 配置方案,从简单到复杂:
方案一:允许所有域名跨域(适用于开发环境或公开 API)
这是最基础的配置,允许任何域名访问,并且处理了 OPTIONS 预检请求。
在你需要允许跨域的 location 块中添加以下代码:
plaintext
server {
listen 80;
server_name api.example.com;
location / {
# 1. 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
# 允许客户端携带的自定义请求头
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
# 预检请求的缓存时间(秒),在此期间浏览器无需再次发送预检请求
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204; # 告诉浏览器预检成功,无需返回实际内容
}
# 2. 处理实际的请求(GET/POST等)
# 注意:加上 always 参数,确保即使后端返回 4xx/5xx 错误,也会带上跨域头
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
# 允许浏览器读取的响应头
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
# 这里是你原本的代理配置
proxy_pass http://localhost:8080;
}
}
方案二:指定单一域名且允许携带 Cookie(适用于生产环境)
如果你的跨域请求需要携带 Cookie 或认证信息(withCredentials: true),Access-Control-Allow-Origin 绝对不能设置为 *,必须指定确切的域名,并且需要加上 Access-Control-Allow-Credentials 'true'。
plaintext
location / {
if ($request_method = 'OPTIONS') {
# 替换为你前端所在的具体域名
add_header 'Access-Control-Allow-Origin' 'https://www.yourfrontend.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
# 允许携带 Cookie
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://www.yourfrontend.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://localhost:8080;
}
方案三:动态允许多个指定域名跨域(高级)
如果你有多个前端域名(例如 a.com 和 b.com)需要访问 API,且需要携带 Cookie,你可以结合 Nginx 的 map 指令来动态匹配 Origin。
第一步:在 http 块中(server 块之外)定义 map:
plaintext
http {
# ... 其他配置 ...
# 检查请求的 Origin 是否在允许的列表中
map $http_origin $cors_origin {
default ""; # 默认不允许
"~^https://www\.domainA\.com$" $http_origin; # 精确匹配
"~^https://www\.domainB\.com$" $http_origin;
"~^http://localhost:[0-9]+$" $http_origin; # 允许本地开发的所有端口
}
server {
# ...
}
}
第二步:在 location 块中使用定义好的 $cors_origin 变量:
plaintext
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';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://localhost:8080;
}
⚠️ 注意事项与避坑指南
- 配置后记得重载 Nginx:
修改完成后,使用nginx -t测试配置是否正确,然后运行nginx -s reload使配置生效。 add_header的覆盖问题(重要):
Nginx 中如果当前块(如if或location)包含add_header指令,它会清除掉所有从父级继承来的add_header。这就是为什么在if ($request_method = 'OPTIONS')中我们要把所有头再写一遍的原因。- 不要在应用层(如 Spring Boot, Node.js)和 Nginx 中同时配置 CORS:
如果你在后端代码里已经写了 CORS 逻辑,又在 Nginx 里配了一遍,浏览器会收到两个Access-Control-Allow-Origin头,从而报错(The 'Access-Control-Allow-Origin' header contains multiple values)。推荐只在 Nginx 层或者只在后端应用层做一次处理。 always参数:
在处理实际请求的add_header后面加上always。这样即使你的后端服务返回 400、401 或 500 错误,Nginx 也会把跨域头加上。否则,前端只能看到 "CORS error",无法获取到真正的 4xx/5xx 错误状态码。