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 应用。

怎么配

  1. 先看清反代发的是 X-Forwarded-* 还是 RFC 7239 Forwarded,边缘代理要清掉客户端伪造的两类头。
  2. server.forward-headers-strategy=framework 时,Boot 默认 spring.mvc.forwarded-headers.header-format=x-forwarded;反代走标准头就改成 standard。
  3. 需要外网 context path 时再开 spring.mvc.forwarded-headers.use-forwarded-prefix=true,并确认代理会发 X-Forwarded-Prefix。
  4. WebFlux 对应属性是 spring.webflux.forwarded-headers.header-format,语义相同。
  5. 手写 Filter 时不要再调无参构造;new ForwardedHeaderFilter(false) 对应 X 族,true 对应标准 Forwarded。

对照结论:A 默认族认 X-Forwarded-*;B 同配置下 Forwarded 静默无效;C 切到 standard 后两者对调。升 Boot 4.2 / Framework 7.1 后,代理头族必须写进配置,不能靠「两种都试一遍」的旧默认。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。