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

1

4

In my Spring Boot application, I was using RestTemplate to call a WS for which the body HMAC signature should be provided as HTTP header. To do this I was using a ClientHttpRequestInterceptor. Basically, I did:

@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);
    }
}

Now I want to use WebClient for a better integration in my reactive application. But I'm lost in this new reactive API. How can I achieve this using ExchangeFilterFunction or BodyInserter? The difficulty is to retrieve the body, to perform the signature computation and to update the request consequently.

Thank you for your support.

Hunt answered 17/12, 2019 at 20:0 Comment(0)
R
1

Signing the body would require it in serialized form, but serialization happens just before sending the data so it needs to be intercepted.

You can do this by creating your own Encoder (wrapping the existing Jackson2JsonEncoder for example) and passing this as an ExchangeStrategies when building the WebClient. After the serialized data is intercepted, you can inject the headers. But the Encoder does not have a reference to the ClientHttpRequest so you will need to capture this object in an HttpConnector and pass it in the SubscriberContext.

This blog post explains the process: https://andrew-flower.com/blog/Custom-HMAC-Auth-with-Spring-WebClient#s-post-data-signing

As an example, your WebClient creation step might look like below, where MessageCapturingHttpConnector is a connector that captures the ClientHttpRequest and BodyCapturingJsonEncoder

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();
Resorcinol answered 23/5, 2020 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.