Search code examples
javaspringhttpresttemplatehttp-status-code-401

Spring RestTemplate's 401 Response redacted (contain's [no body]) even though it has a body


Background: As a part of an API request cycle, let say, for OTP verification, we make a call to an external API (xxxx.com), that returns a 401 if the OTP is invalid, along with a JSON Response.

We use the Spring's RestTemplate, to make external calls to xxxx.com and we want to capture the 401s response as a Java Object but we Fail to do so. We see that the response is redacted.

The RestTemplate we use is from the JAR org.springframework:spring-web:6.1.5

The line that makes calls:

this.restTemplate.exchange("https://xxxx.com", HttpMethod.PUT, entity, responseType);

How we capture non-2XXs:

catch (HttpClientErrorException e) {
    if (successfulHttpStatuses.contains(e.getStatusCode())) {
        return e.getResponseBodyAs(responseType);
    } else {
        log.error("{} returned a {} status - {}", clientName, e.getStatusCode(), e.getMessage());
        throw e;
    }

Our Client works perfectly for 2XXs, just that during 401s, we get the log,

XXXClient returned a 401 status - [no body]

where as we expect the response to Have a body.

Please see this: Image Showing the request, via Postman, returning a response on 401

Also the external xxxx.com we mentioned uses a Bearer Authentication, and throws 401s as well for an invalid JWT.

Response headers don't contain "WWW-Authenticate", we assume the JWT we used is a valid JWT

So, I believe this is a deliberate design decision, to mask 401 responses for security reasons. Is there a work around for this situation?


Solution

  • I looked deeper into the 401 [no body] issue,

    I saw that Spring's Default RestTemplate implementation initializes its constructor by using org.springframework.http.client.SimpleClientHttpRequestFactory, which is based on the JDK's default (java.net) implementation that throws a java.net.HttpRetryException from here.

    See java.net.HttpURLConnection#HTTP_UNAUTHORIZED.

    So, the method org.springframework.web.client.DefaultResponseErrorHandler#getResponseBody via spring, ignores all exceptions and thus returns an empty byte array even though we have a response body.

    As a fix, I had to use Apache's HttpClient Implementation by using org.springframework.http.client.HttpComponentsClientHttpRequestFactory instead, by doing this

    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
    

    This affects all streaming mode operations like POST, PUT where we dont generally get the content-length in the reponse header.

    References: https://github.com/spring-projects/spring-boot/issues/40359