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?
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