springamazon-cloudfrontspring-webclient400-bad-request

AWS CloudFront returning bad request from Spring WebClient but working from Postman


I've an API that is up and running in AWS CloudFront. When I test the API from Postman, it seems to be working fine.

Here's the request from Postman console.

enter image description here

But when invoking the same API from spring's WebClient, it is returning 400 bad request with response:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>400 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Bad request.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: pGx0NHPSaU3H2EHJwtQrYEdLDjL_UPxO90esPoH3d9efZX_bvzjFQw==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

Here's my code:

@Component
public class SyncTranscriberClient {

    private final WebClient webClient;

    @Value("${transcriber.sync.username}")
    private String username;

    @Value("${transcriber.sync.password}")
    private String password;

    public SyncTranscriberClient(@Value("${transcriber.sync.base-url}") String baseUrl) throws SSLException {
        var sslContext = SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();

        var client = HttpClient.create().secure(t -> t.sslContext(sslContext));

        webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(client))
                .baseUrl(baseUrl)
                .filter(logRequest())
                .build();
    }

    private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
            return Mono.just(clientRequest);
        });
    }

    public Map<String, String> getCredentials() {
        return webClient.post()
                .uri("my-url-hidden-for-security-reasons")
                .headers(headers -> headers.setBasicAuth(username, password))
                .headers(headers -> headers.set("Accept", "*/*"))
                .headers(headers -> headers.set("Host", "hidden-for-security-reasons"))
                .retrieve()
                .onStatus(HttpStatus::isError, response -> response.bodyToMono(String.class) // error body as String or other class
                        .flatMap(error -> Mono.error(new RuntimeException(error)))
                )
                .toBodilessEntity()
                .map(HttpEntity::getHeaders)
                .map(headers -> Map.of(
                        "Authorization", Objects.requireNonNull(headers.getFirst("Authorization")),
                        "JSESSIONID", Objects.requireNonNull(headers.getFirst("JSESSIONID"))
                ))
                .block();
    }

}

What am I doing wrong here?


Solution

  • I had to send Content-Type: text/plain and an empty body.

    webClient.post()
                    .uri("my-url-hidden-for-security-reasons")
                    .headers(headers -> {
                        headers.setBasicAuth(username, password);
                        headers.set("Content-Type", "text/plain");
                    })
                    .body(BodyInserters.fromValue(""))
                    .retrieve()
                    .onStatus(
                            HttpStatus::isError,
                            response -> response.bodyToMono(String.class)
                                    .flatMap(error -> Mono.error(new RuntimeException(error)))
                    )
                    .toBodilessEntity()
                    .map(HttpEntity::getHeaders)
                    .map(headers -> Map.of(
                            "Authorization", Objects.requireNonNull(headers.getFirst("Authorization")),
                            "JSESSIONID", Objects.requireNonNull(headers.getFirst("JSESSIONID"))
                    ))
                    .block();