Spring WebClient:自动计算 body 的 HMAC 签名并将其作为 header 传递

Spring WebClient: Automatically compute HMAC signature for body and pass it as header

在我的 Spring 引导应用程序中,我使用 RestTemplate 调用一个 WS,body HMAC 签名应作为 HTTP header 提供。为此,我使用了 ClientHttpRequestInterceptor。基本上,我做了:

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    try {
        String hmac = Hmac.calculateRFC2104HMAC(body, key);
        request.getHeaders().add("X-Hub-Signature", "sha1=" + hmac);
        return execution.execute(request, body);
    }
    catch (NoSuchAlgorithmException | InvalidKeyException e) {
        throw new IOException(e);
    }
}

现在我想使用 WebClient 更好地集成到我的反应式应用程序中。但是我迷失在这个新的反应 API 中。如何使用 ExchangeFilterFunctionBodyInserter 实现此目的?困难在于检索 body、执行签名计算并随后更新请求。

感谢您的支持。

签署 body 需要序列化形式,但序列化发生在发送数据之前,因此需要拦截。

您可以通过创建自己的编码器(例如包装现有的 Jackson2JsonEncoder)并在构建 WebClient 时将其作为 ExchangeStrategies 传递来实现。截取序列化数据后,可以注入headers。但是编码器没有对 ClientHttpRequest 的引用,因此您需要在 HttpConnector 中捕获此 object 并将其传递到 SubscriberContext.

这篇博客post解释了这个过程:https://andrew-flower.com/blog/Custom-HMAC-Auth-with-Spring-WebClient#s-post-data-signing

例如,您的 WebClient 创建步骤可能如下所示,其中 MessageCapturingHttpConnector 是一个连接器,用于捕获 ClientHttpRequestBodyCapturingJsonEncoder

Signer signer = new Signer(clientId, secret);
MessageSigningHttpConnector httpConnector = new MessageSigningHttpConnector();
BodyCapturingJsonEncoder bodyCapturingJsonEncoder
    = new BodyCapturingJsonEncoder(signer);

WebClient client
    = WebClient.builder()
               .exchangeFunction(ExchangeFunctions.create(
                       httpConnector,
                       ExchangeStrategies
                              .builder()
                               .codecs(clientDefaultCodecsConfigurer -> {
                                   clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyCapturingJsonEncoder);
                                   clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
                               })
                               .build()
               ))
               .baseUrl(String.format("%s://%s/%s", environment.getProtocol(), environment.getHost(), environment.getPath()))
               .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
               .build();