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

Spring声明式Rest Http Interface下ExchangeFilterFunction获取请求体并添加签名头

问题描述

使用Spring声明式Rest Http Interface调用内部API时,需要为每个出站请求生成签名并添加到请求头中。尝试通过ExchangeFilterFunction实现该逻辑,但无法获取ClientRequest的真实请求体(调用request.body().toString()得到的并非实际内容),且未直接使用RestTemplate或WebClient,需要一套集中式的解决方案。

尝试的代码示例:

@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {

    // 无法像Spring MVC那样获取有效请求体
    String requestBody = request.body().toString();
    var signatureValue = signRequest(requestBody);

    // 添加Signature请求头
    var headers = new HttpHeaders();
    headers.add("signature", signatureValue);

    var modifiedRequest = ClientRequest.from(request)
        .headers(httpHeaders -> httpHeaders.addAll(headers)).build();
    
    return next.exchange(modifiedRequest);

}
解决方案

方案一:读取并缓存请求体,重新构建请求

ClientRequest的body()返回的是BodyInserter,直接调用toString()无法获取实际内容。需要先执行BodyInserter读取请求体,再重新构建请求体避免内容被消耗,具体实现如下:

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientRequest;
import org.springframework.http.client.reactive.ClientResponse;
import org.springframework.http.client.reactive.ExchangeFilterFunction;
import org.springframework.http.client.reactive.ExchangeFunction;
import org.springframework.http.BodyInserters;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;

@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
    // 读取请求体内容到缓冲区
    return DataBufferUtils.join(request.body().insert(new DefaultDataBufferFactory(), new HttpHeaders()))
        .flatMap(dataBuffer -> {
            // 从缓冲区提取字节并转为字符串
            byte[] bodyBytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bodyBytes);
            DataBufferUtils.release(dataBuffer);
            String requestBody = new String(bodyBytes, StandardCharsets.UTF_8);

            // 生成签名
            String signatureValue = signRequest(requestBody);

            // 重新构建请求体,防止原内容被消耗
            var newBody = BodyInserters.fromValue(requestBody);

            // 修改请求头并构建新请求
            ClientRequest modifiedRequest = ClientRequest.from(request)
                .header("signature", signatureValue)
                .body(newBody)
                .build();

            return next.exchange(modifiedRequest);
        });
}

// 你的签名生成逻辑
private String signRequest(String requestBody) {
    // 示例:根据实际需求替换为真实签名算法
    return "sign_" + requestBody.hashCode();
}

注意事项

  • 该方案适用于文本类型请求体(如JSON、Form表单),如果是二进制请求体,需调整字节解析逻辑。
  • 若担心JSON序列化一致性问题,可使用WebClient配置的ObjectMapper重新序列化请求体,确保签名时的字符串与实际发送内容完全一致。

方案二:全局配置WebClient过滤器(集中式生效)

声明式Rest Http Interface基于WebClient实现,可通过配置全局WebClient过滤器让所有接口自动应用签名逻辑:

1. 配置WebClient及过滤器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.BodyInserters;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;

@Configuration
public class RestClientConfig {

    @Bean
    public WebClient signedWebClient(WebClient.Builder builder) {
        return builder
            .filter(signatureFilter())
            .build();
    }

    private ExchangeFilterFunction signatureFilter() {
        return (request, next) -> {
            return DataBufferUtils.join(request.body().insert(new DefaultDataBufferFactory(), new HttpHeaders()))
                .flatMap(dataBuffer -> {
                    byte[] bodyBytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bodyBytes);
                    DataBufferUtils.release(dataBuffer);
                    String requestBody = new String(bodyBytes, StandardCharsets.UTF_8);
                    String signature = signRequest(requestBody);

                    var newBody = BodyInserters.fromValue(requestBody);
                    ClientRequest modifiedRequest = ClientRequest.from(request)
                        .header("signature", signature)
                        .body(newBody)
                        .build();
                    return next.exchange(modifiedRequest);
                });
        };
    }

    private String signRequest(String requestBody) {
        // 真实签名逻辑
        return "sign_" + requestBody.hashCode();
    }
}

2. 关联声明式接口与WebClient

以Spring Boot 3.2+的@HttpExchange接口为例,注册接口时绑定配置好的WebClient:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class InternalApiConfig {

    @Bean
    public InternalApiClient internalApiClient(WebClient signedWebClient) {
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(signedWebClient))
            .build();
        return factory.createClient(InternalApiClient.class);
    }
}

// 声明式接口示例
@HttpExchange
public interface InternalApiClient {

    @PostExchange("/internal/api/endpoint")
    Mono<ApiResponse> callInternalApi(@RequestBody ApiRequest request);
}

这样所有通过InternalApiClient发起的请求都会自动添加签名头,实现集中式处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:00:08