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.
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();