openApiGateway 跨域 403 问题
记录时间:2026-08-21
环境:测试环境 /openApiGateway(Spring Cloud Gateway)
相关组件:WAF → Nginx →gzeport-openApi-gateway→ 后端服务
一、问题现象
测试环境一个接口访问异常,浏览器发的 OPTIONS 预检请求:
OPTIONS http://192.168.111.x/openapiGateway/newCasLogin/casBackend/login
直接返回 403,响应体是:
{"code":403,"msg":"跨域请求不被允许"}
这明显是跨域被拦了。我先在脑子里把这条链路过了一遍:
浏览器 → WAF → Nginx → openApiGateway → 后端服务
403 到底卡在哪一层,光看现象是看不出来的,只能一层一层往下排。
二、排查过程
2.1 先翻 Nginx 配置
我的第一反应是 Nginx 层。翻了对应的 location:
location /openapiGateway {
rewrite ^/openapiGateway(.*)$ $1 break;
proxy_pass http://192.168.120.x:31803;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
}
这里 rewrite 把前缀去掉了,请求 /openapiGateway/newCasLogin/casBackend/login 会转成 192.168.120.x:31803/newCasLogin/casBackend/login 再转发。配置里没有对这个接口单独的 deny 或 return 403,Nginx 这边看不出问题。
2.2 走 Nginx 用 curl 验证
接着我用 curl 模拟浏览器的 OPTIONS 预检:
curl -i -X OPTIONS \
'http://192.168.111.x/openapiGateway/newCasLogin/casBackend/login' \
-H 'Origin: http://113.xxx.xxx.190:21080' \
-H 'Access-Control-Request-Method: POST'
返回:
HTTP/1.1 403
Server: nginx/1.10.3
Content-Type: application/json;charset=UTF-8
Vary: Origin
响应体还是 {"code":403,"msg":"跨域请求不被允许"}。
这里我提醒自己一句:返回头里有 Server: nginx,不能就咬定是 Nginx 拦的。Nginx 只是反向代理,完全可能把上游 Gateway 返回的 403 原样转发回来,这个头说明不了问题。
2.3 绕过 Nginx 直连 Gateway
为了搞清楚到底是不是 Nginx/WAF 的问题,我干脆绕过 Nginx,直接打 Gateway 的地址 192.168.120.x:31803:
curl -i -X OPTIONS \
'http://192.168.120.x:31803/newCasLogin/casBackend/login' \
-H 'Origin: http://113.xxx.xxx.190:21080' \
-H 'Access-Control-Request-Method: POST'
结果还是 403,响应体一模一样。
到这一步我心里有数了:绕过 Nginx 还是 403,那 WAF、Nginx、Nginx 的 location 配置这三层都可以排除,403 基本就是 openApiGateway 应用自己产生的。
2.4 翻技术部新增代码
既然是 Gateway 自己拦的,我就去翻技术部最近的提交 bc26b19b,发现他们新加了两个过滤器:
gzeport-openApi-gateway/
└── src/main/java/com/gzeport/openApi/gateway/filter/
├── CorsWebFilter.java
└── IpFilter.java
也就是说 Gateway 最近多了两类访问控制:Origin/CORS 白名单,还有 IP 白名单。
2.5 盯住 CorsWebFilter 的匹配逻辑
看 CorsWebFilter.java,白名单的来源有两块,一块是代码里写死的默认值,一块是 Nacos 配置:
private static final String[] DEFAULT_ALLOWED_ORIGINS = {
"http://test.example.cn",
"https://example.cn"
};
@Value("${cors.allowed-origins:}")
private String allowedOriginsConfig;
@PostConstruct 里把默认值和 Nacos 配置合并成 allowedOrigins 这个 Set,启动时会打一行日志 Gateway CORS白名单已加载。
请求进来后的判断是:
String origin = request.getHeaders().getFirst("Origin");
if (origin != null && !allowedOrigins.contains(origin)) {
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().set("Content-Type", "application/json;charset=UTF-8");
response.getHeaders().set("Vary", "Origin");
String body = "{\"code\":403,\"msg\":\"跨域请求不被允许\"}";
return response.writeWith(...);
}
我盯住的就是 allowedOrigins.contains(origin) 这一行。它是 Set 的精确字符串匹配,不是前缀匹配,也不会忽略端口。
2.6 对启动日志,找到真凶
既然代码是精确匹配,那问题大概率出在白名单的值和浏览器实际 Origin 对不上。翻 Gateway 启动日志:
Gateway CORS白名单已加载:
[http://113.xxx.xxx.190,
http://test.example.cn,
https://example.cn]
而浏览器实际发的 Origin 是:
Origin: http://113.xxx.xxx.190:21080
对上了:白名单配的是 http://113.xxx.xxx.190,实际请求带的是 http://113.xxx.xxx.190:21080,多了个 :21080 端口。精确匹配下这两个字符串不相等,于是进了 FORBIDDEN 分支。
三、根因分析
根因一句话:技术部配置的 Gateway CORS 白名单 Origin 和前端实际 Origin 不一致,白名单漏了端口。
链路是:
openApiGateway
↓
CorsWebFilter
↓
Origin 精确匹配失败
↓
HTTP 403
所以这次 403 和 WAF、Nginx、网络、防火墙都无关,就是 Gateway 应用代码自己返回的。
四、解决方案
代码里 @Value("${cors.allowed-origins:}") 说明白名单是可以通过 Nacos 配的。把实际 Origin 补进配置就行:
cors:
allowed-origins: "http://113.xxx.xxx.190:21080"
如果需要同时放行带端口和不带端口的,用逗号分隔:
cors:
allowed-origins: "http://113.xxx.xxx.190,http://113.xxx.xxx.190:21080"
Properties 格式则写:
cors.allowed-origins=http://113.xxx.xxx.190:21080
这里有个要注意的点:白名单是在 @PostConstruct 里一次性构建的,代码里没有看到对 allowedOrigins 的动态刷新实现。所以改了 Nacos 之后不能想当然认为 Gateway 会立即生效,需要先确认项目的 Nacos 动态刷新机制,必要时重启 Gateway 再看启动日志。
五、验证
本次排查的定位验证靠两步:绕过 Nginx 直连 Gateway 仍 403(排除了基础设施层),加上白名单日志和实际 Origin 的对比(定位到端口不一致)。
修复后复测,用原来的 curl 命令,重点看是否还返回 403 跨域请求不被允许:
# 走 Nginx
curl -i -X OPTIONS \
'http://192.168.111.x/openapiGateway/newCasLogin/casBackend/login' \
-H 'Origin: http://113.xxx.xxx.190:21080' \
-H 'Access-Control-Request-Method: POST'
# 绕过 Nginx 直连 Gateway
curl -i -X OPTIONS \
'http://192.168.120.x:31803/newCasLogin/casBackend/login' \
-H 'Origin: http://113.xxx.xxx.190:21080' \
-H 'Access-Control-Request-Method: POST'
同时看 Gateway 启动日志,确认白名单已经包含带端口的 Origin:
grep "Gateway CORS白名单已加载" gateway.log
待确认:本次只定位到根因,改 Nacos 白名单后的复测和是否需重启 Gateway 尚未执行确认。
六、注意事项
6.1 Gateway 现在是两层访问控制
技术部这次同时加了两个过滤器,访问链路变成:
WAF(放行)
↓
Nginx(放行)
↓
Gateway CorsWebFilter(Origin 白名单)
↓
Gateway IpFilter(IP 白名单)
↓
后端服务
所以后面遇到 Gateway 403,要先分清是哪一层拦的。
6.2 按响应体快速定位
返回 {"code":403,"msg":"跨域请求不被允许"} 时,查 CorsWebFilter,对比白名单 Origin 和浏览器实际 Origin,注意端口:
grep "Gateway CORS白名单已加载" gateway.log
返回 {"code":403,"msg":"请求IP不在白名单中!"} 时,查 IpFilter,重点看这几个配置项:
| 参数 | 默认值 | 说明 |
|---|---|---|
globalFilter.ipList |
无 | IP 列表 |
globalFilter.allowFlag |
false |
是否启用 IP 白名单 |
globalFilter.allowIpList |
空 | 允许的 IP 列表,逗号分隔 |
IP 白名单相关日志:
grep -E "clientIp:|不在白名单" gateway.log
6.3 责任边界
这次已经用绕过 Nginx 直连 Gateway 验证过,直连 192.168.120.x:31803 照样 403,所以不是基础设施层的问题。技术部新增 Origin/IP 白名单后,Gateway 自己具备了主动返回 403 的能力,只要 Origin 或客户端 IP 没配进白名单,就算 WAF、Nginx 都放行了,请求照样被应用层拒绝。后续发布时技术部需要同步明确:允许的 Origin、允许的客户端 IP、对应 Nacos 配置、是否需要重启、白名单是否支持动态刷新。
6.4 建议的排查顺序
以后遇到 Gateway 403,不建议一上来就查 WAF,按这个顺序来:
① 看 HTTP 响应 Body,区分 CorsWebFilter 还是 IpFilter
② 绕过 Nginx/WAF 直连 Gateway(192.168.120.x:31803)
③ 看 Gateway 日志:clientIp / Origin / 白名单
④ 最后再查 WAF / Nginx / 网络 / 防火墙
七、参考资料
- 本次排查主要靠翻 Gateway 源码(
CorsWebFilter.java、IpFilter.java)和启动日志定位,未单独查阅外部文章。 - 背景可参考 Spring Cloud Gateway 官方 CORS 配置文档:CORS Configuration