Springboot2 升级Springboot3.3.5 网关篇
- 升级后 GlobalFilter 修改 header 报 UnsupportedOperationException
- 背景:
- debug 排查后问题初步分析
- **问题分析**
- **✅ 解决方案**
- **✅ 方案 1:使用 `ServerHttpRequestDecorator`**
- **❌ 方案 2:关闭 WebFlux 防火墙(不推荐)**
- **🔚 结论**
升级后 GlobalFilter 修改 header 报 UnsupportedOperationException
原代码如下: 使用 exchange.mutate()
ServerHttpRequest.Builder builder = exchange.getRequest().mutate().header(DumboConstants.TRACE_KEY, traceContext.getTraceId()).header(DumboConstants.SPAN_KEY, String.valueOf(traceContext.getSpanId()));
背景:
- 在Springboot 2 系列中,通过mutate() 之后是可以产生一个新的可变的 ServerHttpRequest 对象,可以对请求头等一些相关属性进行调整修改。也是spring 官方推荐的方式
- 在升级Springboot3x 之后,通过exchange.mutate()之后修改header 属性一直抛 UnsupportedOperationException。 不能去修改属性。
debug 排查后问题初步分析
在 Spring Boot 2.x 中,exchange.getRequest().mutate().header(…) 是允许的,但在 Spring Boot 3(Spring 6) 之后,这种方式会导致 UnsupportedOperationException。
在 Spring Boot 3 中,exchange.getRequest() 返回 StrictFirewallServerWebExchange,而在 Spring Boot 2 中,它返回的是 Netty 相关的 ServerHttpRequest, Spring Boot 3 引入了更严格的安全防护机制(StrictFirewallServerWebExchange),导致 mutate() 方法无法像以前一样修改请求头。
问题分析
- Spring Boot 3 采用了
StrictFirewallServerWebExchange:StrictFirewallServerWebExchange是 Spring WebFlux 6 新引入的防火墙交换对象。- 这个对象比之前的 Netty
ServerHttpRequest更加严格,不允许修改某些请求属性(比如mutate()修改header)。 - 可能的原因:Spring Boot 3 默认开启了 WebFlux Firewall,导致
mutate()变得受限。
- 在 Spring Boot 2 中,
exchange.getRequest()直接是 Netty 的ServerHttpRequest:- 允许
mutate().header(...),因为它并未受到StrictFirewallServerWebExchange保护。
- 允许
✅ 解决方案
✅ 方案 1:使用 ServerHttpRequestDecorator
由于 exchange.getRequest().mutate() 可能会在 StrictFirewallServerWebExchange 下抛出 UnsupportedOperationException,我们需要手动创建一个新的 ServerHttpRequest,并返回新的 ServerWebExchange:
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取原始请求头HttpHeaders headers = new HttpHeaders();headers.putAll(exchange.getRequest().getHeaders());headers.add("Custom-Header", "NewValue"); // ✅ 添加自定义请求头// 使用装饰器创建新的 ServerHttpRequestServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic HttpHeaders getHeaders() {return headers;}};// ✅ 替换 ServerWebExchange 的 requestServerWebExchange mutatedExchange = exchange.mutate().request(decoratedRequest).build();return chain.filter(mutatedExchange);}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE; // 确保 GlobalFilter 运行顺序最低,避免冲突}
}
✅ 为什么这个方法可行?
-
ServerHttpRequestDecorator避开了StrictFirewallServerWebExchange的限制:StrictFirewallServerWebExchange可能禁止mutate()修改ServerHttpRequest,但不会阻止ServerHttpRequestDecorator的getHeaders()方法返回自定义的HttpHeaders。
-
不会修改原始
ServerHttpRequest,兼容 Netty/WebFlux 处理机制:- 通过装饰器模式,原始请求对象保持不变,而新的
ServerHttpRequestDecorator仅在getHeaders()方法中返回修改后的HttpHeaders。
- 通过装饰器模式,原始请求对象保持不变,而新的
-
适用于 Spring Boot 3.3.5 及以后版本:
- 这一方案已经适配 Spring Boot 3.x + Spring WebFlux,无论
StrictFirewallServerWebExchange是否存在,都可以工作。
- 这一方案已经适配 Spring Boot 3.x + Spring WebFlux,无论
❌ 方案 2:关闭 WebFlux 防火墙(不推荐)
如果你确定 StrictFirewallServerWebExchange 影响了你的业务逻辑,可以在 application.yml 里关闭它:
yaml
spring:webflux:strict-firewall:enabled: false
❌ 但不推荐这么做,因为 Spring 3 引入这个防火墙是为了提升安全性,直接关闭可能导致:
- 某些非法请求不再被拦截(比如
CRLF注入攻击)。 - 影响其他 WebFlux 组件的安全策略。
🔚 结论
| 方案 | 适用场景 | 解决方式 |
|---|---|---|
| ✅ 方案 1(推荐) | Spring Boot 3.3.5 + WebFlux Firewall 限制 mutate() | 使用 ServerHttpRequestDecorator 手动修改请求头 |
| ❌ 方案 2(不推荐) | 想临时恢复 Spring Boot 2 行为,但有安全风险 | 关闭 spring.webflux.strict-firewall.enabled=false |
🚀 最终建议:
- 优先使用
ServerHttpRequestDecorator(方案 1),这是一种安全、兼容性好、符合 WebFlux 机制的做法。 - 只有在 确定 WebFlux Firewall 影响业务,并愿意承担安全风险 的情况下,才考虑 方案 2(关闭防火墙)。
