Vert.x 5.2.3(2026-11-03)修了一处很实在的缓存隔离问题:共享的 CachingWebClient 以前会把带 Authorization 的响应当普通可缓存内容塞进共享层;同客户端上另一个 WebClientSession 再请求同一 URL,可能直接拿到别人的正文。5.2.3 起,没有 public / s-maxage / must-revalidate 时,这类响应不再进共享缓存,只会进 session 私有层。Spring Boot 4.1.2 对照实测:5.2.2 上 Bob 拿到 Alice 的响应;5.2.3 上各拿各的。

许可证 EPL-2.0 / Apache-2.0。仓库 eclipse-vertx/vert.x,Web Client 在 vert-x3/vertx-web。主源见 5.2.3 官博、Release Notes(CVE-2026-118489),对应改动 ca6e0891(同批还有 Set-Cookie 不进共享层等)。

场景

网关、BFF、多租户服务里,常见写法是一个共享 CachingWebClient,外面再套按用户创建的 WebClientSession,用 session 头带上 Authorization。上游若回 Cache-Control: max-age=…,却没标 private / no-store,旧版共享层会按 URL 把响应存下来。

问题在于:session 是在真正发请求时才把 Authorization 贴上去的,缓存拦截器以前没把它算进「这是认证请求」。结果 Alice 先打到的正文,Bob 用同一个 CachingWebClient 再打同一路径,共享层直接命中,正文还是 Alice 那份。

RFC 9111 §3.5 要求:共享缓存对带 Authorization 的请求,除非响应带 public、s-maxage 或 must-revalidate,否则不该存。5.2.3 按这个收紧;带 Set-Cookie 的响应也不再进共享层。

Boot 4.1.2 双版本对照

同一台机器、JDK 21.0.12.1、Spring Boot 4.1.2。两个独立进程,只改 vertx.version(5.2.2 / 5.2.3),其余代码相同:本机起一个回 max-age=60 且正文为 Authorization|UUID 的 HttpServer;共享 CachingWebClient 上挂 Alice、Bob 两个 session,分别加 Bearer alice / Bearer bob,各打两轮同一路径。

WebClient caching = CachingWebClient.create(WebClient.create(vertx));
WebClientSession alice = WebClientSession.create(caching);
WebClientSession bob = WebClientSession.create(caching);
alice.addHeader("Authorization", "Bearer alice");
bob.addHeader("Authorization", "Bearer bob");

String alice1 = alice.get(port, "127.0.0.1", "/profile").send().await().bodyAsString();
String bob1 = bob.get(port, "127.0.0.1", "/profile").send().await().bodyAsString();
String alice2 = alice.get(port, "127.0.0.1", "/profile").send().await().bodyAsString();
String bob2 = bob.get(port, "127.0.0.1", "/profile").send().await().bodyAsString();

依赖只钉 web-client(会拉齐 vertx-core 等):

<properties>
  <vertx.version>5.2.2</vertx.version>
  <!-- 或 5.2.3 -->
</properties>
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-client</artifactId>
  <version>${vertx.version}</version>
</dependency>

5.2.2(摘录):

:: Spring Boot ::                (v4.1.2)
VERTX_WEB_CLIENT_IMPL_VERSION=5.2.2
CACHE_CONTROL=max-age=60
SCENARIO=shared CachingWebClient + two WebClientSession Authorization headers
ALICE_1=Bearer alice|65101280-ac02-4897-9fa8-134800829ba7
BOB_1=Bearer alice|65101280-ac02-4897-9fa8-134800829ba7
ALICE_2=Bearer alice|65101280-ac02-4897-9fa8-134800829ba7
BOB_2=Bearer alice|65101280-ac02-4897-9fa8-134800829ba7
ORIGIN_HITS=1
CROSS_USER_LEAK=YES

5.2.3(摘录):

:: Spring Boot ::                (v4.1.2)
VERTX_WEB_CLIENT_IMPL_VERSION=5.2.3
CACHE_CONTROL=max-age=60
SCENARIO=shared CachingWebClient + two WebClientSession Authorization headers
ALICE_1=Bearer alice|f3bdecb1-3562-44e1-b6e8-1d783ced9d8b
BOB_1=Bearer bob|138b344a-b2af-4080-a5ee-8b83ac27bd1d
ALICE_2=Bearer alice|f3bdecb1-3562-44e1-b6e8-1d783ced9d8b
BOB_2=Bearer bob|138b344a-b2af-4080-a5ee-8b83ac27bd1d
ORIGIN_HITS=2
CROSS_USER_LEAK=NO
ALICE_SESSION_CACHE_HIT=true
BOB_SESSION_CACHE_HIT=true

旧版源站只打了 1 次,Bob 的两轮正文都等于 Alice 那份 UUID。新版源站打了 2 次,Bob 拿到自己的 Bearer bob|…;各自第二轮仍命中本 session 私有缓存,不是「缓存全废」。

升级时注意

Boot 不会替你管 Vert.x 版本,显式把 vertx-web-client(以及同线的 vertx-core 等)抬到 5.2.3。4.x 线要看对应已修版本(公告写的是到 4.5.35 / 5.2.2 受影响)。

若业务依赖「认证请求也能在共享层命中」,且响应只有 max-age、没有 public / s-maxage / must-revalidate,升级后共享命中会少,源站 QPS 可能上来。真要共享缓存认证响应,按 RFC 把允许指令写清楚;用户私有数据继续用 private / no-store,或别在多用户之间共用同一个 CachingWebClient。

同日 5.2.3 还带了 Netty 4.2.20.Final,以及 STOMP / Redis 客户端两处堆耗尽修复。Netty 行为点可对照 Netty 4.2.20 HttpVersion;本稿只盯 CachingWebClient 这一处可对照的串用户行为。

— 感谢阅读 —
广告

一起交流

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