简体   繁体   English

来自 java.net.http.HttpClient 调用的不可靠响应使用 Java 11 和 Spring 启动

[英]Unreliable response from java.net.http.HttpClient call using Java 11 and Spring Boot

I'm writing a back-end application in Spring Boot that calls another API from a third party.我正在 Spring Boot 中编写一个后端应用程序,该应用程序从第三方调用另一个 API。

I'm having a problem with this particular call, which retrieves a token object containing a bearer token, which then I use in their other endpoints.我遇到了这个特定调用的问题,它检索了一个包含不记名令牌的令牌 object,然后我在他们的其他端点中使用它。 The retrieved token sometimes works, most of the time it doesn't, when calling the other endpoints, resulting in an unauthorized response.在调用其他端点时,检索到的令牌有时会起作用,但大多数时候不会,从而导致未经授权的响应。

@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;
    }

}

And here's an example token object:这是一个示例令牌 object:

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

Retrieving tokens using postman works perfectly fine , so it couldn't be an issue from the third party API.使用postman检索令牌可以正常工作,因此第三方 API 不会有问题。 I also have this same service implemented in .NET Core 3 and it also works perfectly fine over there.我也在.NET Core 3 中实现了同样的服务,它在那里也可以正常工作。

What confuses me the most is that the actual HttpClient call works, I do get a correct Json which is mapped to my TokenResponse object just fine.最让我困惑的是,实际的 HttpClient 调用有效,我确实得到了正确的 Json,它映射到我的 TokenResponse object 就好了。 It's just that the token value is invalid... sometimes.只是令牌值无效......有时。

I've also tried using RestTemplate and WebClient Spring libraries, but the results are the same.我也尝试过使用 RestTemplate 和 WebClient Spring 库,但结果是一样的。 Call works, but retrieved token is invalid.通话有效,但检索到的令牌无效。

At first I thought I was having a race condition, since initially I had another HttpClient in there with another endpoint using the response from the token call.起初我以为我遇到了竞争条件,因为最初我在那里有另一个 HttpClient 和另一个端点,使用来自令牌调用的响应。 So I simplified it into only the token call and manually copying the token value into postman requests.所以我将其简化为仅令牌调用,并将令牌值手动复制到 postman 请求中。 Didn't work.没用。

Then I thought maybe my HttpClient authorization header was malformed, but that's disproven since simply copying the token into a protected endpoint using a postman request shows that the token doesn't work.然后我想也许我的 HttpClient 授权 header 格式不正确,但这是不正确的,因为使用 postman 请求将令牌复制到受保护的端点就表明令牌不起作用。

And other things I've tried:以及我尝试过的其他事情:

  • Pasting the form string I generate in the controller into the Postman request to make sure it's valid.将我在 controller 中生成的表单字符串粘贴到 Postman 请求中,以确保它有效。
  • Check that the URLEncoder is not messing up any form values.检查 URLEncoder 没有弄乱任何表单值。
  • Copy the token value from the token object to use in another endpoint with Postman.从令牌 object 复制令牌值,以在另一个端点中使用 Postman。
  • Skip object mapping and return a simple String, and manually copying the token value from the response in Postman so I can then use it in another endpoint.跳过 object 映射并返回一个简单的字符串,然后手动从 Postman 的响应中复制令牌值,以便我可以在另一个端点中使用它。

I'm pretty lost at this point, only thing that comes to mind is that maybe the HttpClient.send() method might be parsing the body in a way that might be affecting the content?在这一点上我很迷茫,唯一想到的是,也许 HttpClient.send() 方法可能正在以一种可能会影响内容的方式解析正文? I doubt it, but I don't see what else could be happening.我对此表示怀疑,但我看不出还会发生什么。

The solution was related to cookies!解决方案与 cookie 有关!

The token server response was sending 2 set-cookie headers which in Postman and .NET Core were being automatically handled and set to subsequent HTTP requests.令牌服务器响应正在发送 2 个set-cookie标头,这些标头在 Postman 和 .NET Core 中被自动处理并设置为后续的 HTTP 请求。 The 3rd party API was behind a load balancer and generating these session cookies.第 3 方 API 位于负载均衡器后面并生成这些 session cookies。

I solved this by implementing a system-wide CookieHandler with the following code in my main method.我通过在我的main方法中使用以下代码实现系统范围的CookieHandler解决了这个问题。

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

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

Then building my HttpClient object like this:然后像这样构建我的 HttpClient object:

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

This way, the response set-cookie and request cookie headers will be handled automatically and work across all calls made by this HttpClient.这样,响应set-cookie和请求cookie标头将被自动处理,并在此 HttpClient 进行的所有调用中起作用。

By default, CookieHandler is created with a CookiePolicy.ACCEPT_ORIGINAL_SERVER parameter.默认情况下,CookieHandler 是使用CookiePolicy.ACCEPT_ORIGINAL_SERVER参数创建的。 My understanding is that this makes cookies work only if they are set and requested by the same host.我的理解是,这使得 cookies 仅在同一主机设置和请求时才起作用。 Check out the docs for more options on CookiePolicy查看文档以获取有关CookiePolicy的更多选项

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM