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
相关产品推荐
相关产品推荐

