Справочная информация. В рамках цикла запроса API, скажем, для проверки OTP, мы вызываем внешний API (xxxx.com), который возвращает 401, если OTP недействителен, вместе с ответом JSON.
Мы используем Spring RestTemplate для внешних вызовов xxxx.com и хотим захватить ответ 401 как объект Java, но нам не удается это сделать. Мы видим, что ответ отредактирован.
Используемый нами RestTemplate взят из JAR org.springframework:spring-web:6.1.5
Линия, которая осуществляет вызовы:
this.restTemplate.exchange("https://xxxx.com", HttpMethod.PUT, entity, responseType);
Как мы захватываем не-2XX:
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;
}
Наш клиент отлично работает для 2XX, только во время 401 мы получаем журнал,
XXXClient returned a 401 status - [no body]
где, как мы ожидаем, ответ будет иметь тело.
Посмотрите это: Изображение: Показан запрос через Postman, возвращается ответ на номер 401
Кроме того, внешний сайт xxxx.com, о котором мы упоминали, использует аутентификацию по носителю и также выдает 401 в случае неверного JWT.
Итак, я считаю, что это намеренное дизайнерское решение, призванное замаскировать ответы 401 по соображениям безопасности. Есть ли обходной путь для этой ситуации?
Кажется, это проблема не с вашим кодом входа, а скорее с ответом. Заглянув в код здесь org.springframework.web.client.DefaultResponseErrorHandler#getErrorMessage, вы увидите, что [ no body ] возвращается, когда тело ответа пусто.




Как уже упоминалось в моем комментарии, похоже, это проблема, поскольку ответ действительно пуст. Я собрал два теста, чтобы доказать свою точку зрения.
@Test
public void unauthorizedRequestWithBodyTest() {
this.server
.expect(ExpectedCount.once(), requestTo("/unauthorized"))
.andExpect(method(HttpMethod.PUT))
.andRespond(withStatus(HttpStatus.UNAUTHORIZED).body(
" { \"message\" : \"Something is broken\", \"status\": \"401\" } "));
try {
restTemplate.exchange("unauthorized", HttpMethod.PUT, null, String.class);
} catch (HttpClientErrorException e) {
System.out.println("Response with body content:");
System.out.printf("%s returned a %s status - %s\n\n", "clientName", e.getStatusCode(), e.getResponseBodyAsString());
}
}
@Test
public void unauthorizedRequestWithoutBodyTest() {
this.server
.expect(ExpectedCount.once(), requestTo("/unauthorized"))
.andExpect(method(HttpMethod.PUT))
.andRespond(withStatus(HttpStatus.UNAUTHORIZED));
try {
restTemplate.exchange("unauthorized", HttpMethod.PUT, null, String.class);
} catch (HttpClientErrorException e) {
System.out.println("Response without body content:");
System.out.printf("%s returned a %s status - %s\n", "clientName", e.getStatusCode(), e.getMessage());
System.out.printf("%s returned a %s status - %s\n\n", "clientName", e.getStatusCode(), e.getResponseBodyAsString());
}
}
Результат следующий:
Response with body content:
clientName returned a 401 UNAUTHORIZED status - { "message" : "Something is broken", "status": "401" }
Response without body content:
clientName returned a 401 UNAUTHORIZED status - 401 Unauthorized: [no body]
clientName returned a 401 UNAUTHORIZED status -
Из реализации вы можете видеть, что метод getErrorMessage() возвращает текст [no body], когда тело ответа пусто.
if (ObjectUtils.isEmpty(responseBody)) {
return preface + "[no body]";
}
org.springframework.web.client.DefaultResponseErrorHandler#getErrorMessage
спасибо за это глубокое погружение и, указав на DefaultResponseErrorHandler, я попробовал протестировать аналогичные заметки. Не могли бы вы дать мне информацию о том, почему это дало ответ, если я повторил тот же запрос с почтальоном (изображения в вопросе выше)
Также @LostKatana Я попробовал реализовать тот же сценарий, развернув два приложения локально. Приложение A имело конечную точку, похожую на unauthorizedRequestWithBodyTest() Приложение B записывало ответ. В этом случае, что интересно, я получил [Нет тела]
Насчет вашего первого комментария со ссылкой на скриншот я не могу сказать наверняка, но возможно у почтальона другая реализация и поэтому он показывает тело. Что касается вашего второго комментария, я не уверен, правильно ли я понял.
Я глубже изучил проблему 401 [нет тела],
Я видел, что реализация Spring по умолчанию RestTemplate инициализирует свой конструктор с помощью org.springframework.http.client.SimpleClientHttpRequestFactory, который основан на реализации JDK по умолчанию (java.net), которая выдает исключение java.net.HttpRetryException отсюда.
Смотрите java.net.HttpURLConnection#HTTP_UNAUTHORIZED.
Итак, метод org.springframework.web.client.DefaultResponseErrorHandler#getResponseBody через Spring игнорирует все исключения и, таким образом, возвращает пустой массив байтов, даже если у нас есть тело ответа.
В качестве исправления мне пришлось использовать реализацию Apache HttpClient, используя вместо этого org.springframework.http.client.HttpComponentsClientHttpRequestFactory, сделав это
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Это влияет на все операции потокового режима, такие как POST, PUT, где мы обычно не получаем длину содержимого в заголовке ответа.
Использованная литература: https://github.com/spring-projects/spring-boot/issues/40359
Можете ли вы предоставить более подробную информацию о том, как вы устанавливаете токен носителя в заголовке запроса?