深刻理解 forward-headers-strategy
场景复盘(你的案例):
- 你在项目里用了 Springdoc Open Api + Scalar(接口文档 UI)。
- 在 Scalar 的 Test Request 功能里发起调用时,页面提示 failed。
- 打开浏览器控制台/Network 看到请求返回 403。
- 最终通过在 Spring Boot 中增加配置:
1
2 server:
forward-headers-strategy: framework解决问题。
1. 它到底解决什么问题?——“反向代理后的真实请求信息”
当你的服务部署在 反向代理 / Ingress / Gateway / LB 后面时,客户端看到的地址通常是:
https://api.example.com
但你的 Spring Boot 应用实际收到的连接可能是:
- 代理到后端用
http - Host 变成了内网域名/服务名
- 端口、scheme、client ip 都不等于用户真实访问的
这会导致一个核心问题:应用层对“当前请求的绝对地址/来源”的判断失真。
而绝对地址、来源判断又被大量框架能力依赖,例如:
- OpenAPI / Swagger / Scalar 生成的
servers.url(以及 UI 里发请求的目标地址) - Spring Security 对
requiresSecure()、重定向地址、登录回调、某些策略判断 - CORS/CSRF/Origin/Referer 的校验与策略(常见表现之一就是 403)
- 生成链接(重定向、分页链接、Location header)
代理是怎么把“真实信息”告诉后端的?
常见两套标准:
- RFC 7239:
Forwarded头- 例:
Forwarded: proto=https;host=api.example.com;for=1.2.3.4
- 例:
- 事实标准:
X-Forwarded-*- 例:
X-Forwarded-Proto: httpsX-Forwarded-Host: api.example.comX-Forwarded-Port: 443X-Forwarded-For: 1.2.3.4
- 例:
Spring Boot 是否“信任并使用”这些 header,取决于 **forward-headers-strategy**。
2. forward-headers-strategy 的三个值:none / native / framework
Spring Boot(2.6+ / 3.x)用 server.forward-headers-strategy 来决定:
在什么层面解析并应用
Forwarded/X-Forwarded-*,从而修正HttpServletRequest里看到的 scheme/host/port。
2.1 none(默认常见)
- 不处理 forwarded headers。
- 应用看到的就是“代理到后端的真实连接信息”(往往是
http、内网 host)。
风险/表现:
- 文档 UI 可能生成错误地址;
- 安全策略、重定向、绝对 URL 生成可能异常;
- 在某些组合下会出现 403(看起来像权限问题,实则是“请求被判定为不合法来源/不安全”。)
2.2 native
- 依赖 Servlet 容器/服务器本身 来处理 forwarded headers。
- 典型:Tomcat 的
RemoteIpValve、Undertow/Jetty 的对应机制。
优点:
- 更靠近网络层,处理更早。
缺点:
- 对不同容器表现不一致;
- 在一些组合(例如你引入了 Spring Cloud Gateway、或某些 Filter 顺序/封装请求)下,未必如你预期。
2.3 framework(你最终使用的方案)
- 由 Spring Framework 在应用层统一处理。
- 本质是启用/等价于
ForwardedHeaderFilter(或同类机制),将Forwarded/X-Forwarded-*应用到当前请求视图。
优点:
- 行为在 Spring 体系内更一致;
- 对 “OpenAPI/文档 UI 生成地址” 这类应用层能力更直接;
- 对你这个 springdoc + Scalar 的场景,通常是最稳的兜底选项。
缺点:
- 你需要确保:只有在可信代理链路中才会让这些 header 生效(安全问题见下文)。
3. 为什么你的场景会体现为 Scalar Test Request 403?(核心机制)
Scalar 的 Test Request 并不是“随便发个 curl”,它往往依赖 OpenAPI 文档里的 server 信息、当前页面 origin、以及浏览器的同源/CORS 约束,来决定请求要打到哪里。
当 forward-headers-strategy 没开时,Spring Boot 可能把当前请求视为:
http://<internal-host>:<port>
于是 springdoc/Scalar 生成或推导出的请求目标地址出现偏差,例如:
- scheme 从
https变成http - host 从公网域名变成内网域名
- port 从 443 变成容器端口
这类“地址失真”会进一步触发:
- 浏览器侧:跨域限制、混合内容(https 页面去请求 http)、预检失败
- 服务端侧:Spring Security 认为请求不满足安全约束/来源策略,直接 403
你看到的“403”经常是结果,不是原因。
根因是:应用在代理后没拿到“真实的外部访问视图”,导致文档 UI 发起的请求不符合预期安全策略或路由。
启用 framework 后,Spring 会把 X-Forwarded-Proto=https、X-Forwarded-Host=... 等正确映射回 request.getScheme()/getServerName()/getServerPort(),从而:
- springdoc 生成的 server url 更接近外部真实地址
- Scalar Test Request 命中正确的 https + 正确域名
- 安全策略与路由恢复一致,403 消失
4. 推荐配置:在“有代理”的服务上,优先 framework
4.1 最小可用(你现在的修复)
1 | server: |
4.2 配套建议(强烈建议你核对)
- 确保代理正确设置 forwarded headers
Nginx 示例(仅示意):
1 | proxy_set_header Host $host; |
Ingress / LB 也要确认是否自动注入 X-Forwarded-* 或 Forwarded。
- 只在可信代理链路中使用
forwarded headers 是可伪造的。如果你的应用直接暴露在公网且未经过可信代理,攻击者可以手工构造:
X-Forwarded-Proto: httpsX-Forwarded-Host: evil.com
从而影响你生成的链接、重定向、安全判断等。
因此原则是:
- 应用只接受来自可信代理的流量(网络层限制)
- 或在代理层清洗/重写这些 header(只保留代理自己注入的)
- 别同时“重复处理”
避免同时开了容器层(native)又开框架层(framework)造成重复修正,出现奇怪的 host/port。
5. 排查清单(下次遇到 403/回调地址异常,可以快速定位)
5.1 先看请求在服务端“被认为是什么”
在某个接口里临时打印(或加一个 debug endpoint):
request.getScheme()request.getServerName()request.getServerPort()request.getRequestURL()- 以及 headers:
Forwarded、X-Forwarded-*
如果你从外部用 https://api.example.com 访问,但服务端看到的是 http://10.x.x.x:8080,那 90% 就是 forwarded headers 没生效。
5.2 再看文档 UI 发请求打到哪里
- 浏览器 Network 面板里看请求 URL
- 如果 URL 不是你期望的公网 https 域名,而是内网/HTTP/端口不对,优先修 forwarded headers
6. 结论(给你的落地建议)
- 你的问题属于典型的“服务在代理后,但应用没有正确恢复外部请求视图”,在 springdoc + Scalar 这种强依赖 server/origin 的 UI 上,最容易表现为 Test Request failed + 403。
- 在 Spring Boot 体系里,**
server.forward-headers-strategy=framework** 通常是最稳定、最一致的选择。 - 同时要确保:
- 代理确实注入了正确的
Forwarded/X-Forwarded-* - 网络/代理层保证这些 header 不会被客户端伪造利用
- 代理确实注入了正确的
附:这个案例的一句话复盘
线上通过反向代理访问服务,Scalar Test Request 返回 403。根因是应用未启用 forwarded headers 解析,导致 Spring 认为请求 scheme/host 为内网 http,从而文档 UI 生成/发起的请求地址与安全策略不一致。设置
server.forward-headers-strategy=framework后,Spring 正确使用X-Forwarded-*恢复外部访问视图,问题解决。