前言:跨域请求为什么Cookie会丢失?
做过前后端分离项目的人基本都踩过同一个坑:前端用axios或fetch发起跨域请求,后端明明设置了Cookie,但浏览器就是收不到。打开开发者工具一看,Set-Cookie响应头有了,可Request Headers里就是没有Cookie字段。
这不是bug,这是浏览器的安全机制。CORS(跨域资源共享)默认不允许跨域请求携带凭据(Cookie、Authorization头等),需要在服务端和前端同时配置才能打通。而Nginx作为反向代理层,承担着关键的跨域头配置职责。
这篇文章就把Nginx CORS + Cookie携带的配置讲透,从原理到实战,从常见坑到排查方法,一次搞定。
一、CORS携带Cookie的三个必要条件
要让跨域请求成功携带Cookie,必须同时满足以下三个条件,缺一不可:
1. 服务端响应头设置正确
Access-Control-Allow-Origin:不能设为*,必须指定具体的源(如https://www.example.com)Access-Control-Allow-Credentials: true:明确允许携带凭据Access-Control-Allow-Headers:如需自定义头(如Token),必须在这里声明
2. 前端请求设置withCredentials
XMLHttpRequest:xhr.withCredentials = true
axios:axios.get(url, { withCredentials: true })
fetch:fetch(url, { credentials: 'include' })
3. Cookie的SameSite和Secure属性配置正确
Chrome 80以后默认SameSite=Lax,跨域请求不会发送Cookie。需要后端设置Cookie时:
SameSite=None(允许跨域携带)Secure(必须配合HTTPS使用)
这三个条件中,Nginx主要负责第一个——响应头的正确设置。下面看具体怎么配。
二、Nginx CORS Cookie基础配置
最基本的配置方案:
server {
listen 443 ssl;
server_name api.example.com;
# CORS响应头配置
add_header Access-Control-Allow-Origin 'https://www.example.com' always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Requested-With' always;
add_header Access-Control-Max-Age 3600 always;
# 处理预检请求
if ($request_method = OPTIONS) {
return 204;
}
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
几个关键点:
- always参数必须加:不加always的话,4xx/5xx错误响应不会带CORS头,前端拿不到错误信息
- Origin不能用星号:
Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true不能同时存在,这是CORS规范的硬性要求 - 预检请求直接返回204:OPTIONS请求不需要转发到后端,Nginx直接处理即可
三、多域名动态Origin配置
实际项目中往往有多个前端域名需要跨域访问API,比如开发环境、测试环境、生产环境。这时不能用一个固定Origin,需要用Nginx的map指令动态匹配:
# 定义允许的Origin白名单
map $http_origin $cors_origin {
default "";
"~^https://www\.example\.com$" "https://www.example.com";
"~^https://admin\.example\.com$" "https://admin.example.com";
"~^https://test\.example\.com$" "https://test.example.com";
"~^http://localhost:\d+$" $http_origin;
}
server {
listen 443 ssl;
server_name api.example.com;
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Requested-With' always;
add_header Access-Control-Max-Age 3600 always;
# 只有白名单内的Origin才设置CORS头
if ($cors_origin = "") {
add_header Access-Control-Allow-Origin "" always;
}
if ($request_method = OPTIONS) {
return 204;
}
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这个方案的核心是map指令:根据请求的Origin动态返回对应的值,不在白名单内的Origin返回空字符串。注意这里处理了一个细节——未匹配的Origin不设置Allow-Origin头,而不是设置为*。
四、Cookie SameSite问题的Nginx层处理
如果你的后端(如Java Spring、Node.js Express、PHP)设置的Cookie没有指定SameSite=None; Secure,浏览器在跨域请求时不会携带这个Cookie。有两种解决思路:
方案一:后端修改(推荐)
让后端在Set-Cookie时加上SameSite=None; Secure:
- Java:
response.setHeader("Set-Cookie", "sessionId=xxx; Path=/; SameSite=None; Secure; HttpOnly") - Node.js:
res.cookie('sessionId', 'xxx', { sameSite: 'none', secure: true, httpOnly: true }) - PHP:
setcookie('sessionId', 'xxx', ['samesite' => 'None', 'secure' => true, 'httponly' => true])
方案二:Nginx代理修改Cookie属性
如果后端不方便改,可以在Nginx层用proxy_cookie_path间接处理:
location / {
proxy_pass http://backend;
# 修改后端返回的Cookie,追加SameSite和Secure属性
proxy_cookie_path / "/; SameSite=None; Secure";
}
这个技巧利用了proxy_cookie_path的路径替换机制,在Cookie的path后面追加了SameSite和Secure属性。虽然有点取巧,但在无法修改后端代码时是一个有效的补救方案。
五、Nginx add_header继承覆盖问题
这是配置CORS+Cookie时最容易踩的坑。Nginx的add_header指令有继承规则:
- 子块(location)有add_header时,父块(server)的add_header不会继承
- 子块没有add_header时,会继承父块的所有add_header
什么意思呢?看这个有问题的配置:
server {
add_header Access-Control-Allow-Origin 'https://www.example.com' always;
add_header Access-Control-Allow-Credentials 'true' always;
location /api/ {
# 这里加了一个自定义头,导致父级的CORS头全部失效!
add_header X-Custom-Header 'value' always;
proxy_pass http://backend;
}
}
在/api/这个location里,因为有了自己的add_header,父级server块的CORS头全部丢失了。解决办法:
# 方案一:在location里重复写CORS头(不推荐,维护成本高)
location /api/ {
add_header Access-Control-Allow-Origin 'https://www.example.com' always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header X-Custom-Header 'value' always;
proxy_pass http://backend;
}
# 方案二:用include统一管理(推荐)
# 把CORS头抽到单独文件 cors.conf
# 然后在需要的location里 include /etc/nginx/conf.d/cors.conf;
六、完整生产环境配置模板
综合以上所有要点,给一个完整的生产可用配置:
# /etc/nginx/snippets/cors.conf - CORS头配置片段
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Requested-With, Accept, Origin' always;
add_header Access-Control-Expose-Headers 'Content-Length, Content-Range, X-Request-Id' always;
add_header Access-Control-Max-Age 86400 always;
# /etc/nginx/conf.d/api.example.com.conf
map $http_origin $cors_origin {
default "";
"~^https://www\.example\.com$" $http_origin;
"~^https://admin\.example\.com$" $http_origin;
"~^http://localhost(:\d+)?$" $http_origin;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
include /etc/nginx/snippets/cors.conf;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials 'true' always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Requested-With, Accept, Origin' always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
location / {
include /etc/nginx/snippets/cors.conf;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cookie_path / "/; SameSite=None; Secure";
}
}
注意这里预检请求(OPTIONS)的if块里也写了CORS头,因为if块相当于一个独立的location,不会继承server块的add_header。这又回到了前面说的继承问题。
七、常见问题排查清单
- Cookie字段在请求头里消失了:检查SameSite属性是否为None、Secure是否为true、是否走了HTTPS
- 浏览器报CORS error但响应头看起来没问题:检查Origin是否精确匹配(包括协议、端口、路径),注意https和http是不同的Origin
- 预检请求404/405:检查后端是否处理了OPTIONS方法,或者Nginx是否正确拦截了预检请求
- 某些location的CORS头丢失:检查add_header继承问题,子块的add_header会覆盖父块的
- Access-Control-Allow-Origin不能设为*:当需要携带Cookie时,必须指定具体Origin
- 多个Set-Cookie只有一个生效:检查每个Cookie是否都设置了SameSite=None; Secure
总结
Nginx CORS与Cookie携带的配置看似简单,但坑不少。核心记住三点:Origin不能为星号、add_header有继承陷阱、Cookie的SameSite和Secure必须配对。按上面的模板配好,基本能覆盖90%的跨域Cookie场景。剩下的10%通常是前后端配置不一致导致的,对照排查清单逐一检查就行。
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论