使用java 11和Spring Boot从java.net.http.HttpClient调用得到的响应不可靠
我正在用Spring Boot编写一个后端应用程序,它调用来自第三方的另一个API 我在这个调用中遇到了一个问题,它检索一个包含承载令牌的令牌对象,然后我在它们的其他端点中使用它。 调用其他端点时,检索到的令牌有时有效,但大多数情况下无效,从而导致未经授权的响应使用java 11和Spring Boot从java.net.http.HttpClient调用得到的响应不可靠,java,spring-boot,http,java-11,java-http-client,Java,Spring Boot,Http,Java 11,Java Http Client,我正在用Spring Boot编写一个后端应用程序,它调用来自第三方的另一个API 我在这个调用中遇到了一个问题,它检索一个包含承载令牌的令牌对象,然后我在它们的其他端点中使用它。 调用其他端点时,检索到的令牌有时有效,但大多数情况下无效,从而导致未经授权的响应 @RestController public class CotizacionController { Logger logger = LoggerFactory.getLogger(CotizacionController.
@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;
}
}
使用邮递员检索代币工作得非常好,因此第三方API不会出现问题。我也在.NET Core 3中实现了这个相同的服务,而且它在那里也运行得非常好
最让我困惑的是,实际的HttpClient调用是有效的,我确实得到了一个正确的Json,它映射到了我的TokenResponse对象。只是令牌值无效。。。有时候
我也尝试过使用RestTemplate和WebClient Spring库,但结果是一样的。调用正常,但检索到的令牌无效
起初,我认为我有一个竞争条件,因为最初我有另一个HttpClient,另一个端点使用来自令牌调用的响应。因此,我将其简化为仅令牌调用,并将令牌值手动复制到postman请求中。没用
然后我想可能我的HttpClient授权头的格式不正确,但这是不正确的,因为使用postman请求简单地将令牌复制到受保护的端点表明令牌不起作用
还有我尝试过的其他事情:
- 将我在控制器中生成的表单字符串粘贴到Postman请求中以确保其有效
- 检查URLEncoder是否没有弄乱任何表单值
- 从令牌对象复制令牌值,以便在另一个具有Postman的端点中使用
- 跳过对象映射并返回一个简单字符串,然后在Postman中手动复制响应中的令牌值,这样我就可以在另一个端点中使用它
在这一点上我很迷茫,我想到的唯一一件事是HttpClient.send()方法可能正在以一种可能会影响内容的方式解析主体?我对此表示怀疑,但我不知道还会发生什么。解决方案与cookies有关 令牌服务器响应正在发送2
set cookie
头,Postman和.NET Core中的这些头被自动处理并设置为后续HTTP请求。
第三方API位于负载平衡器后面,并生成这些会话cookie
我在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时,cookie才能工作。查看文档以获取更多选项,关于您是否有办法验证您收到的访问令牌是否与生成的访问令牌相同(发送帖子的位置)?HttpClient.send()
不解析正文:它应该只发送字节,正如您所期望的那样。但是请注意,默认情况下,bodypublisher.ofString(String)
将字节编码为UTF-8。和BodyHandlers.ofString()
将使用响应头中指示的任何字符集(如果未指定任何字符集,则使用UTF-8)。记录请求/响应头以尝试找出是否有任何奇怪的地方可能会很有趣:-Djdk.httpclient.httpclient.log=“errors,requests,headers”
令牌端点请求和响应是相同的,但我在查看受保护端点的请求头时发现了线索。它有一些cookie是由令牌端点的响应设置的,postman和.Net Core都在自动处理这些cookie,但java HttpClient没有。在尝试受保护的端点之前,我通过手动删除cookies在postman上复制了这个问题,并且得到了相同的问题行为。如果结果与cookies有关,那么在构建客户端时尝试一下。考虑添加一个显示您的解决方案的答案,以防将来其他人遇到同样的问题。
...
HttpClient client = HttpClient.newBuilder().cookieHandler(CookieHandler.getDefault()).build();
...