烟消火灭网

浏览器控制台又跳出那个熟悉的红色报错:Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://w

nginx拦截跨域请求

浏览器控制台又跳出那个熟悉的截跨红色报错:Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://www.example.com' has been blocked by CORS policy。前端同事跑过来问我,域请是截跨不是后端没配好跨域头。我让他先别急着改代码,域请打开终端敲了一行 curl -I https://api.example.com/data,截跨返回的域请响应头里压根没有 Access-Control-Allow-Origin。问题出在 Nginx 这一层——请求还没到后端应用,截跨就被 Nginx 直接拦截了。域请

很多人以为跨域是截跨后端框架该管的事。Spring Boot 加个 @CrossOrigin 注解,域请或者 Django 装个 django-cors-headers 就完事了。截跨但实际生产环境里,域请Nginx 往往作为反向代理挡在最前面。截跨如果 Nginx 配置了某些规则,域请OPTIONS 预检请求可能根本到不了后端。截跨比如下面这段配置,就是常见的“拦截”元凶:

location /api/ {  if ($request_method = OPTIONS) {  return 403; } proxy_pass http://backend; }

这段配置的逻辑很简单:只要请求方法是 OPTIONS,直接返回 403 Forbidden。写这段配置的人可能本意是屏蔽一些扫描器的探测请求,但副作用是把浏览器的 CORS 预检也一并干掉了。浏览器发起的跨域请求,只要不是简单请求,都会先发一个 OPTIONS 请求去问服务器“我能不能跨域”。服务器返回 403,浏览器就认为跨域被拒绝,控制台报错,实际请求根本不会发出。

还有一种情况更隐蔽。Nginx 配置了 add_header 指令,但没处理好继承关系。比如:

location /api/ {  add_header Access-Control-Allow-Origin *; proxy_pass http://backend; }

看起来没问题,但如果你在 server 块或者 http 块里也写了 add_header,Nginx 的规则是:子块中的 add_header 会覆盖父块中的所有 add_header,而不是合并。这意味着如果你在 http 块里加了 add_header X-Frame-Options DENY;,然后在 location 块里只写了 CORS 头,那么 X-Frame-Options 就丢了。反过来也一样,如果你在 location 块里写了安全头,CORS 头可能被覆盖掉。

我见过一个真实案例。某电商网站的前端部署在 static.example.com,API 在 api.example.com。Nginx 配置里有一段:

location /v1/ {  add_header Access-Control-Allow-Origin https://static.example.com; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; if ($request_method = OPTIONS) {  return 204; } proxy_pass http://api_backend; }

看起来挺完整。但前端反馈说带 Authorization 头的请求还是跨域失败。我让他把预检请求的响应头打出来,发现 Access-Control-Allow-Headers 里没有 Authorization。检查 Nginx 配置,发现 add_header 写的是 'Content-Type, Authorization',但实际发出的响应头里只有 Content-Type。原因是 Nginx 在 if 块里 return 204 时,add_header 指令不会生效——return 直接结束了请求处理,后续的 add_header 根本没机会执行。

正确的做法是把 CORS 头放在 if 块内部,或者用 map 指令动态设置。更干净的方式是单独写一个 location 专门处理 OPTIONS:

location /v1/ {  if ($request_method = OPTIONS) {  add_header Access-Control-Allow-Origin https://static.example.com; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; add_header Access-Control-Max-Age 86400; return 204; } add_header Access-Control-Allow-Origin https://static.example.com; proxy_pass http://api_backend; }

这样预检请求直接由 Nginx 返回 204,带上完整的 CORS 头,不经过后端。实际请求再走 proxy_pass。注意 Access-Control-Max-Age 可以缓存预检结果,减少 OPTIONS 请求次数。

还有一点容易忽略:Nginx 的 add_header 默认只对 200、201、204、206、301、302、303、304、307、308 这些状态码生效。如果后端返回 500,CORS 头不会加上,浏览器看到的依然是跨域错误,而不是 500 错误。排查问题时容易被误导。可以用 add_header ... always; 强制对所有状态码生效。

说到底,Nginx 拦截跨域请求这件事,本质上是配置的优先级和继承规则在作怪。别一上来就怀疑后端代码,先看看 Nginx 的 error.log 和 access.log,确认请求到底有没有到后端。如果 OPTIONS 请求的响应码是 403 或者 204 但没有 CORS 头,那问题就在 Nginx 这一层。改配置比改代码快得多。

访客,请您发表评论:

© 2026. sitemap