Ненадежный ответ от вызова java. net .http.HttpClient с использованием Java 11 и Spring Boot - PullRequest
2 голосов
/ 14 июля 2020

Я пишу внутреннее приложение в Spring Boot, которое вызывает другой API от третьего лица.

У меня проблема с этим конкретным вызовом, который извлекает объект токена, содержащий токен-носитель , которые затем я использую в их других конечных точках. Полученный токен иногда работает, в большинстве случаев это не так, при вызове других конечных точек, что приводит к несанкционированному ответу.

@RestController
public class CotizacionController {

    Logger logger = LoggerFactory.getLogger(CotizacionController.class);

    @Value("${service.credentials.tokenServer}")
    private String tokenServer;

    @Value("${service.credentials.grantType}")
    private String grantType;

    @Value("${service.credentials.username}")
    private String username;

    @Value("${service.credentials.password}")
    private String password;

    HttpClient client = HttpClient.newHttpClient();

    @RequestMapping("/create")
    public Object Create() throws IOException, InterruptedException {

        HashMap<String, String> parameters = new HashMap<>();
        parameters.put("grant_type", grantType);
        parameters.put("username", username);
        parameters.put("password", password);

        String form = parameters.keySet().stream()
                .map(key -> key + "=" 
                                + URLEncoder.encode(parameters.get(key), 
                                                    StandardCharsets.UTF_8))
                .collect(Collectors.joining("&"));

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(tokenServer))
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(BodyPublishers.ofString(form)).build();

        HttpResponse<?> response = client.send(request, BodyHandlers.ofString());

        TokenResponse result = new ObjectMapper().readValue(response
                                   .body().toString(), TokenResponse.class);

        return result;
    }

}

А вот пример объекта токена:

{
    "access_token": "z-bu-Pde6M2dlPiaRzd5XpTrT7ohpFQZe157HHVLfdKJWsdmKCloK7AYGEw7SLCe28tjYAxo8MZOE_3W00HEa-bqgUvcrAKfxIubAq0UGXv7jLPWbRwWzhAUCDon3kdstUrJ_OKRN2y26W6qyDBGDqlP5NRSF4unH_pD_ShmpDlSxZdYUqD0da5Y2_uO6YRs5GuWA7XhI9sPa98SxuXN_dwiDJVif418xK646fUgWR8",
    "token_type": "bearer",
    "expires_in": "3599"
}

Получение токенов с помощью почтальона отлично работает , поэтому это не может быть проблемой из стороннего API. У меня также есть эта такая же служба, реализованная в. NET Core 3, и она также отлично работает там.

Что меня больше всего смущает, так это то, что фактический вызов HttpClient работает, я действительно получить правильный Json, который очень хорошо сопоставлен с моим объектом TokenResponse. Просто значение токена недействительно ... иногда.

Я также пробовал использовать библиотеки RestTemplate и WebClient Spring, но результаты те же. Вызов работает, но полученный токен недействителен.

Сначала я думал, что у меня состояние гонки, так как изначально у меня был другой HttpClient с другой конечной точкой, использующей ответ от вызова токена. Поэтому я упростил его до вызова токена и ручного копирования значения токена в запросы почтальона. Не сработало.

Тогда я подумал, что, возможно, мой заголовок авторизации HttpClient был искажен, но это опровергнуто, поскольку простое копирование токена в защищенную конечную точку с помощью запроса почтальона показывает, что токен не работает.

И другие вещи, которые я пробовал:

  • Вставка строки формы, которую я генерирую в контроллере, в запрос Postman, чтобы убедиться, что она действительна.
  • Убедитесь, что кодировщик URLEncoder не испортить какие-либо значения формы.
  • Скопируйте значение токена из объекта токена для использования в другой конечной точке с помощью Postman.
  • Пропустить сопоставление объектов и вернуть простую строку и вручную скопировать значение токена из ответа в Postman, чтобы затем я мог использовать его в другой конечной точке.

Я довольно потерян на этом этапе, единственное, что приходит в голову, это то, что, возможно, метод HttpClient.send () может анализировать тело таким образом, чтобы это могло повлиять на его содержимое? Сомневаюсь, но не понимаю, что еще могло произойти.

1 Ответ

2 голосов
/ 16 июля 2020

Решение было связано с куки-файлами!

Ответ токен-сервера отправлял 2 set-cookie заголовка, которые в Postman и. NET Core автоматически обрабатывались и устанавливались для последующих HTTP-запросов. Сторонний API стоял за балансировщиком нагрузки и генерировал эти файлы cookie сеанса.

Я решил эту проблему, реализовав общесистемный CookieHandler со следующим кодом в моем методе main.

public static void main(String[] args) {
        CookieHandler.setDefault(new CookieManager());

        SpringApplication.run(Main.class, args);
    }

Затем построение моего объекта HttpClient следующим образом:

...
HttpClient client = HttpClient.newBuilder().cookieHandler(CookieHandler.getDefault()).build();
...

Таким образом, заголовки ответа set-cookie и запроса cookie будут обрабатываться автоматически и работать со всеми вызовами, сделанными этим HttpClient.

По умолчанию CookieHandler создается с параметром CookiePolicy.ACCEPT_ORIGINAL_SERVER. Насколько я понимаю, файлы cookie работают только в том случае, если они установлены и запрошены одним и тем же хостом. Ознакомьтесь с документацией, чтобы узнать о дополнительных параметрах CookiePolicy

...