Nginx 或网关把 HTTPS 卸掉之后,应用里 request.getScheme() 还是 http,拼出来的回调地址、Cookie Secure、重定向全错。Spring Boot 4.2.0-RC1 配上 Framework 7.1.0-RC1 之后,server.forward-headers-strategy=framework 这条老路径仍能用,但 ForwardedHeaderFilter 的无参构造已经 @Deprecated(since="7.1", forRemoval=true):必须显式选标准 Forwarded 或 X-Forwarded-* 其中一族。Boot 默认选 x-forwarded;你的反代如果只吐 RFC 7239 的 Forwarded 头,不改 spring.mvc.forwarded-headers.header-format=standard,头等于白带。
Framework 7.1 发行说明写明:无参构造 deprecated,改用 ForwardedHeaderFilter(boolean useStandardHeader);X-Forwarded-Prefix 另由 setUseForwardedPrefix 控制。Boot 4.2 M1 Release Notes 对应落地:FRAMEWORK 策略默认走 "X-Forwarded-*",可用 spring.mvc.forwarded-headers.header-format(WebFlux 是 spring.webflux.forwarded-headers.header-format)切换。枚举只有两个合法值:x-forwarded(X_FORWARDED)和 standard(STANDARD);写成 forwarded 会直接绑属性失败。
环境和依赖
实测:JDK 21.0.12.1、Spring Boot 4.2.0-RC1、Spring Framework 7.1.0-RC1、嵌入式 Tomcat 11.0.27。Boot 4 用 spring-boot-starter-webmvc:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.2.0-RC1</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
启动横幅与容器版本:
:: Spring Boot :: (v4.2.0-RC1)
2026-11-05T08:57:02.715+08:00 INFO ... o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 18080 (http)
2026-11-05T08:57:02.730+08:00 INFO ... o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/11.0.27]
2026-11-05T08:57:02.983+08:00 INFO ... o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 18080 (http) with context path '/'
2026-11-05T08:57:03.013+08:00 INFO ... demo.DemoApplication : Started DemoApplication in 1.286 seconds
一个只回显视角的接口,方便 curl 断言:
@GetMapping("/whoami")
public Map<String, Object> whoami(HttpServletRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("scheme", request.getScheme());
body.put("serverName", request.getServerName());
body.put("serverPort", request.getServerPort());
body.put("contextPath", request.getContextPath());
body.put("requestURI", request.getRequestURI());
body.put("secure", request.isSecure());
body.put("remoteAddr", request.getRemoteAddr());
return body;
}
默认配置(也是对照 A 的基准):
server:
port: 18080
forward-headers-strategy: framework
spring:
mvc:
forwarded-headers:
header-format: x-forwarded # 默认值;可省略
use-forwarded-prefix: false # 默认 false;要用 X-Forwarded-Prefix 再开
元数据里 header-format 默认就是 x-forwarded,use-forwarded-prefix 默认 false。下面三组对照都在同一进程端口上用 curl 打 /whoami。
对照 A:默认 x-forwarded,带 X-Forwarded-*
curl -sS http://127.0.0.1:18080/whoami \
-H 'X-Forwarded-Proto: https' \
-H 'X-Forwarded-Host: api.example.com' \
-H 'X-Forwarded-Port: 443'
{"scheme":"https","serverName":"api.example.com","serverPort":443,"contextPath":"","requestURI":"/whoami","secure":true,"remoteAddr":"127.0.0.1"}
把 use-forwarded-prefix=true 再附上 X-Forwarded-Prefix: /app:
{"scheme":"https","serverName":"api.example.com","serverPort":443,"contextPath":"/app","requestURI":"/app/whoami","secure":true,"remoteAddr":"127.0.0.1"}
不带任何转发头时回到直连视角:scheme=http、serverName=127.0.0.1、serverPort=18080。
对照 B:同一默认配置,只带 RFC 7239 Forwarded
进程仍是 header-format=x-forwarded,请求改成:
curl -sS http://127.0.0.1:18080/whoami \
-H 'Forwarded: proto=https;host="api.example.com:443"'
{"scheme":"http","serverName":"127.0.0.1","serverPort":18080,"contextPath":"","requestURI":"/whoami","secure":false,"remoteAddr":"127.0.0.1"}
和 A 完全不同:Forwarded 头被忽略,应用仍以为自己在明文 18080。这就是「反代配了标准头、应用却默认只认 X 族」时的静默失败。
不写 header-format、只开 server.forward-headers-strategy=framework 时行为与 A/B 相同:X-Forwarded-* 生效,Forwarded 不生效。默认值在元数据里已经钉死。
对照 C:header-format=standard,换族才认 Forwarded
server:
forward-headers-strategy: framework
spring:
mvc:
forwarded-headers:
header-format: standard
use-forwarded-prefix: false
再带 Forwarded 头:
{"scheme":"https","serverName":"api.example.com","serverPort":443,"contextPath":"","requestURI":"/whoami","secure":true,"remoteAddr":"127.0.0.1"}
同配置下改回 X-Forwarded-*(含 Prefix):
{"scheme":"http","serverName":"127.0.0.1","serverPort":18080,"contextPath":"","requestURI":"/whoami","secure":false,"remoteAddr":"127.0.0.1"}
Proto / Host / Port 全部被忽略。注意:use-forwarded-prefix 是独立开关。若在 standard 下把它开成 true,再送 X-Forwarded-Prefix: /app,实测会出现 scheme 仍是 http、但 contextPath 已变成 /app 的半生效状态。Prefix 要跟你选定的那一族反代能力一起规划,别混开。
枚举写错时启动直接失败,合法值只有 STANDARD 与 X_FORWARDED:
Failed to bind properties under 'spring.mvc.forwarded-headers.header-format' to
org.springframework.boot.webmvc.autoconfigure.WebMvcProperties$HeaderFormat:
Property: spring.mvc.forwarded-headers.header-format
Value: "forwarded"
Reason: ... No enum constant ... HeaderFormat.forwarded
The following values are valid:
STANDARD
X_FORWARDED
顺带:NATIVE 与 FRAMEWORK
同一套 X-Forwarded-*,server.forward-headers-strategy=native(Tomcat 原生)也能把 scheme/host/port 改对:
{"scheme":"https","serverName":"api.example.com","serverPort":443,"contextPath":"","requestURI":"/whoami","secure":true,"remoteAddr":"127.0.0.1"}
NATIVE 下只带 Forwarded 头则不改写。Boot 4.2 起原生容器也可以选 header 族;需要 Prefix、或想行为与容器解耦时,继续用 FRAMEWORK 并显式设 header-format。FRAMEWORK 现在主要落在 Spring MVC / WebFlux 应用上,不再泛指所有 Servlet 应用。
怎么配
- 先看清反代发的是
X-Forwarded-*还是 RFC 7239Forwarded,边缘代理要清掉客户端伪造的两类头。 server.forward-headers-strategy=framework时,Boot 默认spring.mvc.forwarded-headers.header-format=x-forwarded;反代走标准头就改成standard。- 需要外网 context path 时再开
spring.mvc.forwarded-headers.use-forwarded-prefix=true,并确认代理会发X-Forwarded-Prefix。 - WebFlux 对应属性是
spring.webflux.forwarded-headers.header-format,语义相同。 - 手写 Filter 时不要再调无参构造;
new ForwardedHeaderFilter(false)对应 X 族,true对应标准Forwarded。
对照结论:A 默认族认 X-Forwarded-*;B 同配置下 Forwarded 静默无效;C 切到 standard 后两者对调。升 Boot 4.2 / Framework 7.1 后,代理头族必须写进配置,不能靠「两种都试一遍」的旧默认。
一起交流
分享你的思考,让讨论更进一步。