0

Nginx CORS与Cookie携带配置教程:解决跨域请求Cookie丢失的完整实战

2026.05.23 | youres | 18次围观

前言:跨域请求为什么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辅助作者原创,未经许可,转载请保留原文链接。

发表评论
883文章数 0评论数
作者其它文章