You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Framework中如何在ExchangeFilterFunction获取入站请求

在Spring WebClient过滤器中获取入站请求对象

问题概述

在Spring Framework API中实现了TokenFilter,其renewTokenFilter方法返回ExchangeFilterFunction,用于给WebClient出站请求注入OAuth2令牌。现在需要在该过滤器触发WebClient调用时,获取到之前的入站Web请求对象,但尝试通过自定义ReactiveRequestContextFilter传递上下文时,过滤器中获取的上下文为空。

现有代码

TokenFilter类

@Service
public class TokenFilter {
    private final WebClient.Builder webClientBuilder;
    private final RequestCache requestCache;
    private static final Logger LOG = LoggerFactory.getLogger(TokenFilter.class);

    public TokenFilter(WebClient.Builder webClientBuilder, RequestCache requestCache) {
        this.webClientBuilder = webClientBuilder;
        this.requestCache = requestCache;
    }

    public ExchangeFilterFunction renewTokenFilter() {
        return (request, next) -> {
            LOG.info("outbound request path: {}", request.url().getPath());
            // 此处需要获取入站请求对象
            return processTokenFilter(request, next);
        };
    }

    // 省略processTokenFilter方法实现
}

WebClient配置类

@Configuration
public class WebClientFilterConfig {
    private static final Logger LOG = LoggerFactory.getLogger(WebClientFilterConfig.class);

    @Autowired
    private WebClient.Builder webClientBuilder;

    @Autowired
    private TokenFilter tokenFilter;

    @PostConstruct
    public void addFilterToWebClient() {
        LOG.info("configure the renewTokenFilter only once in this config");
        webClientBuilder.filter(tokenFilter.renewTokenFilter()).build();
    }
}

业务调用类UserWebClient

public class UserWebClient {
    private static final Logger LOG = LoggerFactory.getLogger(UserWebClient.class);

    private final WebClient.Builder webClientBuilder;
    private final String userByAuthIdEp;

    public UserWebClient(WebClient.Builder webClientBuilder,
                         String userByAuthIdEp) {
        this.webClientBuilder = webClientBuilder;
        this.userByAuthIdEp = userByAuthIdEp;
    }

    public Mono<UUID> getUserId(String authenticationId) {
        StringBuilder userByAuthId = new StringBuilder(userByAuthIdEp.replace("{authenticationId}",
                authenticationId));

        LOG.info("make user call out to endpoint: {}", userByAuthId);

        WebClient.ResponseSpec responseSpec = webClientBuilder.build().get().uri(userByAuthId.toString())
                .retrieve();

        return responseSpec.bodyToMono(Map.class).map(map -> {
            LOG.info("user found: {}", map);
            return UUID.fromString(map.get("id").toString());
        });
    }
}

无效的上下文传递尝试

自定义的ReactiveRequestContextFilter:

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.ANY)
public class ReactiveRequestContextFilter implements WebFilter {
    private static final Logger LOG = LoggerFactory.getLogger(ReactiveRequestContextFilter.class);
    public static final String CONTEXT_KEY = "inboundRequest";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        LOG.error("reactive request: {}", request.getPath());

        return chain.filter(exchange).contextWrite(context -> context.put(CONTEXT_KEY, request));
    }
}

尝试在过滤器中获取上下文(结果为空):

public ExchangeFilterFunction headerFilter() {
    LOG.info("in headerFilter()");
    return (request, next) -> ReactiveRequestContextHolder.getRequest().flatMap(r -> {
        // r为空,无法获取入站请求
        return next.exchange(request);
    });
}

解决方案

问题出在上下文传递时机和WebClient构建方式上,以下是正确的实现步骤:

1. 正确传递Reactor上下文

修改自定义ReactiveRequestContextFilter,确保在处理请求前写入上下文,而非处理完成后:

@Configuration
public class ReactiveRequestContextFilter implements WebFilter {
    private static final Logger LOG = LoggerFactory.getLogger(ReactiveRequestContextFilter.class);
    public static final String INBOUND_REQUEST_CONTEXT_KEY = "inboundRequest";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest inboundRequest = exchange.getRequest();
        LOG.info("Captured inbound request path: {}", inboundRequest.getPath());
        // 在执行请求链前写入上下文,确保后续所有Mono/Flux都能获取到
        return chain.filter(exchange)
                .contextWrite(Context.of(INBOUND_REQUEST_CONTEXT_KEY, inboundRequest));
    }
}

2. 在TokenFilter中正确获取上下文

不要使用ReactiveRequestContextHolder.getRequest()(它为Spring MVC设计,不适用于Reactive场景),直接通过Reactor上下文获取:

@Service
public class TokenFilter {
    private static final Logger LOG = LoggerFactory.getLogger(TokenFilter.class);
    private final RequestCache requestCache;

    // 移除WebClient.Builder注入,避免循环依赖
    public TokenFilter(RequestCache requestCache) {
        this.requestCache = requestCache;
    }

    public ExchangeFilterFunction renewTokenFilter() {
        return (request, next) -> {
            // 从Reactor上下文中获取入站请求
            return Mono.deferContextual(contextView -> {
                ServerHttpRequest inboundRequest = contextView.get(ReactiveRequestContextFilter.INBOUND_REQUEST_CONTEXT_KEY);
                LOG.info("Got inbound request path: {}", inboundRequest.getPath());
                
                // 示例:使用入站请求信息处理出站请求
                String authHeader = inboundRequest.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
                ClientRequest modifiedRequest = ClientRequest.from(request)
                        .header(HttpHeaders.AUTHORIZATION, authHeader) // 可替换为OAuth2令牌逻辑
                        .build();
                
                return next.exchange(modifiedRequest);
            });
        };
    }

    // 省略processTokenFilter方法
}

3. 修正WebClient构建方式

避免每次调用都重新构建WebClient,通过@Bean构建配置好的实例供其他类注入:

// 修改WebClientFilterConfig
@Configuration
public class WebClientFilterConfig {
    private static final Logger LOG = LoggerFactory.getLogger(WebClientFilterConfig.class);

    @Bean
    public WebClient webClient(WebClient.Builder webClientBuilder, TokenFilter tokenFilter) {
        LOG.info("Configuring renewTokenFilter for WebClient");
        return webClientBuilder
                .filter(tokenFilter.renewTokenFilter())
                .build();
    }
}

// 修改UserWebClient,注入WebClient实例而非Builder
public class UserWebClient {
    private static final Logger LOG = LoggerFactory.getLogger(UserWebClient.class);

    private final WebClient webClient;
    private final String userByAuthIdEp;

    public UserWebClient(WebClient webClient, String userByAuthIdEp) {
        this.webClient = webClient;
        this.userByAuthIdEp = userByAuthIdEp;
    }

    public Mono<UUID> getUserId(String authenticationId) {
        String userByAuthId = userByAuthIdEp.replace("{authenticationId}", authenticationId);
        LOG.info("Making user call to endpoint: {}", userByAuthId);

        return webClient.get()
                .uri(userByAuthId)
                .retrieve()
                .bodyToMono(Map.class)
                .map(map -> {
                    LOG.info("User found: {}", map);
                    return UUID.fromString(map.get("id").toString());
                });
    }
}

关键说明

  • Reactor上下文跟随Mono/Flux流传递,必须确保WebClient调用链路在入站请求的上下文范围内执行,不能脱离上下文构建新流。
  • 不要在PostConstruct中调用webClientBuilder.build(),这会提前固化WebClient,导致后续配置修改不生效。
  • ReactiveRequestContextHolder主要用于Spring MVC场景,Reactive环境下使用Mono.deferContextual获取上下文更可靠。

内容的提问来源于stack exchange,提问作者Katlock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:15:58