Implementing Request Encryption with RestClient Interceptors

When implementing request encryption that includes payload data, the conventional approach involves converting the request body to a string format before encryption. Here's a typical pattern:

String payloadString = ModelOptionsUtils.toJsonString(payload);
String hashedRequestPayload = sha256Hex(payloadString);
// Add hashedRequestPayload to headers and include in request
ResponseEntity<String> response = this.restClient.post()
    .uri("/")
    .headers(headers -> {
        headers.addAll(jsonContentHeaders);
    })
    .body(chatRequest)
    .retrieve()
    .toEntity(String.class);

This approach has significant drawbacks. The conversion step ModelOptionsUtils.toJsonString(payload) may not produce identical output to what the RestClient actually serializes, leading to encryption mismatches. When inconsistencies occur, debugging becomes extremely difficult since the encrypted value never matches the actual request body being sent.

Using Interceptors for Consistent Encryption

The solution is to perform encryption within an interceptor, ensuring we process the exact payload that gets transmitted.

RestClient provides better flexibility and control compared to traditional HTTP utilities. Its interceptor mechanism allows seamless manipulation of request and response logic. Here's how to integrate an authentication interceptor:

ApiAuthHttpRequestInterceptor apiAuthInterceptor = new ApiAuthHttpRequestInterceptor();
this.restClient = RestClient.builder(baseUrl)
    .defaultHeaders(jsonContentHeaders)
    .defaultStatusHandler(responseErrorHandler)
    .requestInterceptor(apiAuthInterceptor)
    .build();

The interceptor implementation acceses the raw request body directly:

public class ApiAuthHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                        ClientHttpRequestExecution execution) throws IOException {
        String hashedPayload = sha256Hex(new String(body));
        request.getHeaders().putAll(authHeaders);
        ClientHttpResponse response = execution.execute(request, body);
        return response;
    }
}

This approach eliminates the conversion step entirely. Since we receive the raw byte array that will actually be sent over the wire, the encryption is guaranteed to match the transmitted payload.

How It Works

The underlying mechanism iterates through registered interceptors sequentially, executing each in order before the request is dispatched. This recursive chain ensures all interceptors process the request in sequence. After all interceptors complete their processing, the final request proceeds to the server.

By intercepting at the byte level, we bypass any potential serialization inconsistencies between our conversion logic and the framework's internal serialization, resulting in reliable and predictable encryption.

Tags: RESTClient interceptor Encryption Spring HTTP

Posted on Wed, 12 Aug 2026 16:55:32 +0000 by kaitan