--- tags: [Nginx, 反向代理, 跨域, CORS, 运维] create time: 2026-05-15 18:00 --- # Nginx 反向代理处理跨域 ## 概述 本文档讲解如何利用 Nginx 反向代理解决浏览器端跨域(CORS)问题,从"最干净的方案"到"兜底方案"逐一展开,覆盖生产环境的典型场景。 > [!TIP] 核心思路 > 跨域是浏览器的安全策略,不是后端的限制。Nginx 做反向代理的核心思想:**让浏览器看到的地址和页面同源**,或者 **由 Nginx 在响应头中注入合法的 CORS header**。 ## 正文 ### 三种方案总览 ```mermaid flowchart LR A[遇到跨域问题] --> B{能否统一域名?} B -- 能 --> C["方案一: 同源代理
零跨域, 最干净"] B -- 不能 --> D{后端已设 CORS header?} D -- 能控制后端 --> E["方案二: Nginx 注入
网关统一管理"] D -- 无法改后端 --> F["方案三: 透传 Header
保留上游配置"] ``` --- ### 方案一:同源代理 — 从根本上消灭跨域(推荐) 请求的域名和页面域名完全一致,浏览器根本不认为这是跨域请求。 ```nginx server { listen 80; server_name app.example.com; # 前端静态资源 location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } # API 代理 — 浏览器看到 /api/xxx,和页面同源 location /api/ { proxy_pass http://backend: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; } } ``` **请求链路示意:** ```mermaid sequenceDiagram participant B as 浏览器 participant N as Nginx participant S as 后端服务 B->>N: GET /api/user (同源,无预检) N->>S: GET /api/user S-->>N: 200 OK (body) N-->>B: 200 OK (body,不含 CORS header) ``` | 维度 | 说明 | |------|------| | 是否真正解决跨域 | ✅ 根本解决,浏览器不认为存在跨域 | | Cookie / Session | 天然支持,无需额外配置 | | 安全性 | 最高,不存在任何跨域风险 | | 适用前提 | 你能控制前端部署域名 | > **思考**:如果前端和后端分别部署在不同的子域名(如 `app.example.com` 和 `api.example.com`),这个方案还适用吗?不适用——这就是下面两种方案的用武之地。 --- ### 方案二:Nginx 注入 CORS header(前后端无法同域时) 当 API 确实需要独立域名时,Nginx 充当"CORS 网关",负责注入正确的响应头。 ```nginx server { listen 80; server_name api.example.com; # 允许的合法前端域名列表 set $cors_origin ""; if ($http_origin ~* "^https://(app\.example\.com|dev\.example\.com)$") { set $cors_origin $http_origin; } location /api/ { proxy_pass http://backend:8080; # 白名单校验后的 Origin(未匹配则不注入,防止随意跨域) add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Token" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Max-Age "3600" always; add_header Access-Control-Expose-Headers "X-Total-Count, X-Request-Id" always; # 预检请求直接响应 204 if ($request_method = OPTIONS) { return 204; } } } ``` **为什么这里用了正则白名单而不是直接回显 `$http_origin`?** 因为直接回显虽然省事,但意味着**任意域名**都能通过白名单校验拿到 CORS header,等同于变相开放。通过 `~*` 正则限定具体域名,是安全加固的一层。 #### 关键参数解释 | 参数 | 作用 | 注意事项 | |------|------|---------| | `add_header ... always` | `always` 确保即使后端返回 4xx/5xx 也加上 CORS header | 不加 `always` 时错误响应会丢失跨域信息 | | `set $cors_origin ""` | 初始化为空,默认不允许跨域 | 这是一种"拒绝所有、放行白名单"的安全策略 | | `if ($request_method = OPTIONS)` | 拦截预检请求,避免转发给后端 | Nginx 的 `if` 在此处使用是安全的(官方推荐用法) | | `Access-Control-Max-Age` | 浏览器缓存预检结果的时间(秒) | 开发时建议设为 `0`,生产可设 `3600`~`86400` | > [!WARNING] Credential 模式陷阱 > 如果设置了 `Access-Control-Allow-Credentials: true`,`Access-Control-Allow-Origin` **绝对不能是 `*`**。必须动态回显具体域名或走白名单——上面的配置已经通过白名单规避了这个问题。 --- ### 方案三:透传后端 CORS header 当后端已经正确配置了 CORS header 时,**默认情况下 Nginx 不会移除这些响应头**,所以大多数场景你不需要任何额外配置。 > [!TIP] Nginx Header 传递规则(重点) > - **无 `add_header` 时**:Nginx 完整透传上游响应头(包括 CORS header),且子级 `location` 会继承父级的 `add_header`。 > - **有 `add_header` 时**:Nginx **丢弃所有父级 `add_header` + 上游的 CORS header**。此时必须显式重新添加需要的头。 > > 这是最常见的坑——你在某个 `location` 里加了一个自定义 header,结果发现 CORS header 突然消失了。 ```nginx # 如果你的 Nginx 配置里只有纯粹的 proxy_pass,无需额外操作: location /api/ { proxy_pass http://backend:8080; } ``` 但如果因为其他原因你已经在这个 `location` 里有 `add_header`(比如加了自定义 X-Custom-Header),就需要把 CORS header 一并补上: ```nginx location /api/ { proxy_pass http://backend:8080; # 原有自定义 header add_header X-Request-Id $request_id always; # 补充 CORS header(否则会被 Nginx 吞掉) proxy_hide_header Access-Control-Allow-Origin; if ($http_origin ~* "^https://.+\.example\.com$") { add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials "true" always; } } ``` | 做法 | 效果 | 适用场景 | |------|------|---------| | 不加任何 header | ✅ 完全透传后端 CORS | 后端已配置 CORS,Nginx 只做纯代理 | | 仅加 `proxy_hide_header` | ⚠️ 不发送 CORS header | 需要临时屏蔽后端的 CORS | | 加 `add_header` + `proxy_hide_header` | ✅ 手动重建 CORS | 已在该 location 有其他 header 需共存时 | > [!NOTE] 最佳实践建议 > 如果后端和前端都在你的控制范围内,**统一将 CORS 配置放在后端中间件层**是最推荐的做法。Nginx 只负责路由转发,不做跨域决策——这样配置清晰、易于维护和审计。只有当你无法修改后端代码时,才需要在 Nginx 层"代劳"设置 CORS header(即回到方案二)。 --- ### 常见陷阱与调试技巧 #### 陷阱一:`proxy_pass` 尾部斜杠的语义差异 `location /api/` 和 `proxy_pass` URL 末尾的 `/` 组合会产生不同行为,这是最容易踩的坑。 | `location` | `proxy_pass` 结尾 | 请求 `/api/user` → 转发给后端的路径 | |------------|------------------|-----------------------------------| | `/api/` | 无 `/`(`http://backend:8080`) | `/api/user`(原样转发) | | `/api/` | 有 `/`(`http://backend:8080/`) | `/user`(`/api/` 被截断替换) | > **记忆口诀**:`proxy_pass` URL 末尾有没有 `/`,决定了 location 匹配部分是否会被截断。 > - 没 `/` → 保持前端路径前缀不变(适合前后端共用同一个 API 前缀) > - 有 `/` → 自动去掉 location 前缀(适合后端路由不需要前缀的场景) #### 陷阱二:CORS header 突然消失 — Nginx 的继承规则 Nginx 的 `add_header` 有一个非常反直觉的行为:**一旦你在某个层级定义了 `add_header`,该层级及以下会丢弃所有父级的 header + 上游响应头。** 排查方法: 1. 用 `curl -vI https://api.example.com/api/test` 直接看响应头 2. 如果能看到 CORS header → 是 Nginx 配置层覆盖问题,回到方案三的规则修复 3. 如果看不到 → 检查后端实际返回了哪些 header(可能后端根本没设) #### 陷阱三:`Access-Control-Allow-Headers` 遗漏字段 浏览器预检时会带上自定义 header(如 `X-Token`, `Authorization`),如果 Nginx 白名单中不包含这些字段,预检直接失败: ```bash # 在控制台观察到的典型错误 # Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: # Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response. ``` 解决:确保 `Access-Control-Allow-Headers` 包含前端所有可能发送的自定义 header。 #### 调试 Checklist ```mermaid flowchart TD A[浏览器报 CORS 错误] --> B{curl 能正常访问?} B -- No --> C["检查后端服务是否存活
及健康检查路径"] B -- Yes --> D{"Response Header 有 CORS?"} D -- No --> E["后端未设置 CORS header
→ 在后端中间件中添加"] D -- Yes --> F{"curl 有但浏览器没有?"} F -- Yes --> G["Nginx add_header 覆盖了上游
→ 检查方案三的继承规则"] F -- No --> H["检查 Response Status Code
是否为 2xx/3xx?
(4xx/5xx 需加 always)"] ``` --- ### 对比总结 | 维度 | 方案一(同源代理) | 方案二(Nginx 注入) | 方案三(透传后端) | |------|------------------|--------------------|-------------------| | 是否真正解决跨域 | ✅ 根本解决 | ⚠️ 表面绕过 | ⚠️ 取决于后端 | | 安全性 | 最高(无跨域) | 高(白名单可控) | 低(信任后端) | | Cookie 支持 | ✅ 天然支持 | ✅ 配合白名单可用 | ✅ 天然支持 | | 维护成本 | 低 | 中等(域名变更需改配置) | 最低 | | 推荐优先级 | 🥇 首选 | 🥈 次选 | 🥉 兜底 | ## 关联笔记 - [[hzh/DEV/跨域问题调试]] — 跨域排查完整指南 - [[GIN/3-middleware/cors-registration-scope]] — 后端 Go/Gin 端 CORS 中间件配置参考