找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1753

积分

0

好友

221

主题
发表于 1 小时前 | 查看: 3| 回复: 0

当你遇到跨域问题时,先别急着“抄配置就完事”。建议把这篇文章按顺序看完:从报错入手,逐个补齐响应头,并用 Nginx 代理把常见坑一次性踩明白。

分析前准备

首先保证服务端没有处理跨域;其次,用 Postman 测试服务端接口是正常的。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 1

当网站 8080 去访问服务端接口时就产生了跨域问题。接下来我把跨域中经常遇到的几类情况列举出来,并通过 Nginx 代理的方式处理(后台同理,只要理解原理即可)。

跨域主要涉及的 4 个响应头

  • Access-Control-Allow-Origin:用于设置允许跨域请求的源地址(预检请求与正式请求都会验证)
  • Access-Control-Allow-Headers:跨域允许携带的特殊头信息字段(只在预检请求验证)
  • Access-Control-Allow-Methods:跨域允许的请求方法 / HTTP 动词(只在预检请求验证)
  • Access-Control-Allow-Credentials:是否允许跨域使用 cookies。若要跨域携带 cookies,可添加该响应头并设为 true(是否设置不影响请求发送,只影响跨域时浏览器是否携带 cookies;若设置,预检与正式请求都需要一致返回)。不过一般不建议跨域使用 cookies(有些浏览器场景不稳定),除非确有必要。

很多文章会告诉你“直接在 Nginx 里加几个响应头就能解决跨域”。大多数情况下确实可以,但也经常出现:明明配了,还是报 CORS。为什么?

什么是预检请求(Preflight)?

当触发跨域条件时,浏览器会先发起一个预检请求(通常是 OPTIONS),询问服务器:

  • 当前网页所在的 Origin 是否允许?
  • 允许哪些请求方法(Methods)?
  • 允许哪些自定义请求头(Headers)?

只有预检通过,浏览器才会发出正式的 XMLHttpRequest 请求,否则直接报错。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 2

预检和正式请求本质上都是 HTTP 交互的一部分,所以排查要点永远是:看控制台报错、看 Network 里 OPTIONS 的响应头与状态码

开始动手模拟

Nginx 代理端口:22222,配置如下:

server {
    listen        22222;
    server_name   localhost;
    location  / {
       proxy_pass  http://localhost:59200;
    }
}

先测试代理是否成功:通过 Nginx 代理端口 2222 再次访问接口,可以看到代理后接口也能正常访问。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 3

接下来用网站 8080 访问 Nginx 代理后的接口地址,开始出现跨域报错。


情况 1:预检响应缺少 Access-Control-Allow-Origin

报错示例:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 4

错误信息已经很明确:preflight 说明这是预检请求(一个 OPTIONS 请求),预检的响应里缺少 Access-Control-Allow-Origin

那就补上它。修改 Nginx 配置如下(红色部分为新增逻辑,这里用文字描述:缺什么补什么):

server {
    listen        22222;
    server_name   localhost;
    location  / {
       add_header Access-Control-Allow-Origin 'http://localhost:8080';
       proxy_pass  http://localhost:59200;
    }
}

配置完你可能会发现:还是报同样的问题。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 5

此时配置思路没错,问题出在 Nginx 的 add_header 行为上。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 6

add_header 指令用于添加返回头字段,但默认只有在特定状态码时才生效。如果希望每次响应都携带该头,需要在最后添加 always(实践中通常只有 Access-Control-Allow-Origin 这个头特别容易踩到 always 的坑)。

修改为:

server {
    listen        22222;
    server_name   localhost;
    location  / {
       add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
       proxy_pass  http://localhost:59200;
    }
}

修改后你会发现:这一步生效了,但跨域还没完全解决,因为报错内容变了。


情况 2:预检请求没有拿到 HTTP ok 状态码

报错示例:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 7

这说明浏览器发出的预检(OPTIONS)没有收到一个“ok”状态码。常见做法是:当请求为 OPTIONS 时直接返回 204

server {
    listen        22222;
    server_name   localhost;
    location  / {
       add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {
return 204;
       }
       proxy_pass  http://localhost:59200;
    }
}

配完后报错又会变化。


情况 3:自定义请求头 authorization 未被允许

报错示例:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 8

含义很直白:预检响应头 Access-Control-Allow-Headers 中缺少 authorization。也就是说,浏览器发现你要带自定义头(例如 authorization / token),但服务器没声明“允许”,所以直接拦截。

于是有人会这样改(注意:这段配置保持原样展示):

server {
        listen        22222;
        server_name   localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {               add_header Access-Control-Allow-Headers 'authorization'; #为什么写在if里面而不是接着Access-Control-Allow-Origin往下写?因为这里只有预检请求才会检查               return 204;            }          proxy_pass http://localhost:59200;      }
}

然后你可能会发现:报错又回到了“情况 1”。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 9

为什么会这样?关键点在于:只要在某个层级写了 add_header,就会影响继承规则

经测试验证,只要 if ($request_method = 'OPTIONS') 里面写了 add_header,当为预检请求时,外部配置的会失效。官方文档描述如下:

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

也就是说:当前层级没有 add_header 才继承上一层;一旦当前层级定义了 add_header,就不再继承上一层的 add_header

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 10

因此配置需要在 OPTIONS 分支里把必要的头也补齐:

server {
    listen        22222;
    server_name   localhost;
    location  / {
        add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers 'authorization';
return 204;
        }
        proxy_pass  http://localhost:59200;
    }
}

此时跨域问题就解决了。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 11

不过,上面的写法有个隐患:考虑后期 Nginx 版本更新,不确定规则是否变化;同时也要小心携带了两个 Access-Control-Allow-Origin(这是不允许的,后面会说“情况 5”)。

因此可以进一步把配置拆开,避免重复返回同一个头:

server {
    listen        22222;
    server_name   localhost;
    location  / {
if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers 'authorization';
return 204;
        }
if ($request_method != 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
        }
        proxy_pass  http://localhost:59200;
    }
}

还没完,继续往下。


情况 4:PUT / DELETE 等方法未被允许(Access-Control-Allow-Methods)

有些较早期的 API 只用到了 POSTGET,而 Access-Control-Allow-Methods 若没有覆盖到其他方法,同样会跨域失败。

例如把请求从 GET 改成 PUT,控制台会报:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 12

处理方式依旧是:缺什么补什么。把 PUT 加进 Access-Control-Allow-Methods

server {
    listen 22222;
    server_name localhost;
    location / {
if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers 'content-type,authorization';
            add_header Access-Control-Allow-Methods 'PUT';#为这么只加在这个if中,不再下面的if也加上?因为这里只有预检请求会校验,当然你加上也没事。
return 204;
        }
if ($request_method != 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
        }
        proxy_pass http://localhost:59200;
    }
}

这里再注意一个细节:改成 PUT 后,Access-Control-Allow-Headers 往往会额外校验 content-type(这和情况 3 一样)。所以你会看到上面示例里把 content-type 也补上了;否则就会报类似“content-type 不被允许”的错误。

如果你想省事,Access-Control-Allow-HeadersAccess-Control-Allow-Methods 可以设置为 * 表示全部匹配;但 Access-Control-Allow-Origin 不建议设为 *,从安全角度来说,限制域名很有必要。

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 13

都加上后,跨域层面的问题就解决了。此处返回 405 是因为服务端接口只开放了 GET,没有开放 PUT;你用 PUT 请求它,服务端自然会返回 405

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 14


情况 5:服务端也加了跨域,导致 Access-Control-Allow-Origin 返回多个值

最后说一种很常见、也最容易“互相打架”的情况:后端已经处理了跨域,你又在 Nginx 里处理一次

有些服务端同学会直接在代码里贴一段跨域中间件,但没搞清楚预检、methods、headers、OPTIONS 状态码等细节,导致响应头不完整;这时你再用 Nginx “通用配置”补一遍,就可能出现这种错误:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:8080', but only one is allowed.

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 15

Nginx代理解决CORS跨域:OPTIONS预检与响应头配置 - 图片 - 16

含义是:此刻 Access-Control-Allow-Origin 返回了多个值,但规范只允许一个。遇到这种情况,思路很简单:去掉其中一边的 Access-Control-Allow-Origin

更建议的做法是:跨域要么在服务端解决,要么在 Nginx 代理解决,二选一。混着搞,尤其是不了解原理时,往往越配越乱。

另外注意:如果按上面的写法,if ($request_method = 'OPTIONS') 里的 Access-Control-Allow-Origin 不要删(因为预检请求会直接 return,不会转发到 59200);你应当删除的是 != 'OPTIONS' 分支里重复的那份,避免返回多个值。


参考完整配置(* 号按需填写)

方案一(把头分别放在预检与非预检分支):

server {
    listen        22222;
    server_name   localhost;
    location  / {
if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers '*';
            add_header Access-Control-Allow-Methods '*';
            add_header Access-Control-Allow-Credentials 'true';
return 204;
        }
if ($request_method != 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            add_header Access-Control-Allow-Credentials 'true';
        }
        proxy_pass  http://localhost:59200;
    }
}

方案二(统一加头,然后对 OPTIONS 单独返回 204):

server {
    listen        22222;
    server_name   localhost;
    location  / {
        add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
        add_header Access-Control-Allow-Headers '*';
        add_header Access-Control-Allow-Methods '*';
        add_header Access-Control-Allow-Credentials 'true';
if ($request_method = 'OPTIONS') {
return 204;
        }
        proxy_pass  http://localhost:59200;
    }
}

结语

这篇文章的核心是:按报错定位预检失败原因,再针对性补齐响应头与 OPTIONS 状态码,并理解 Nginx add_header 的继承与 always 规则。掌握这套排查路径后,遇到不同项目、不同请求头/方法组合,也能快速自查自修。

更多同类运维与中间件实战内容,可以到 云栈社区 继续查阅与交流。




上一篇:OpenCode开源AI编程工具上手:安装配置、LSP与实战
下一篇:自动投递简历脚本实战:Java GUI覆盖Boss等平台
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 19:12 , Processed in 0.208812 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表