java - 使用 Java 11 和 Spring Boot 调用 java.net.http.HttpClient 的响应不可靠
问题描述
我正在 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() 方法可能正在以一种可能会影响内容的方式解析正文?我对此表示怀疑,但我看不出还会发生什么。
解决方案
解决方案与 cookie 有关!
令牌服务器响应发送 2 个set-cookie
标头,在 Postman 和 .NET Core 中这些标头被自动处理并设置为后续 HTTP 请求。第 3 方 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的更多选项
推荐阅读
- html - 如何在 nextjs 中添加自定义 Css
- python - How to sort a dictionary by relative word frequency in two txt files
- c - Sort the array according to the alphabetic order of the last names of the students
- sql-server - 为 SQL Server 绑定的 CSV 格式化 BIGQUERY 输出
- mysql - 如何使用 MySQL 查询从表中选择除一列之外的所有内容?
- javascript - 使用 JS + CSS 的主动字体粗细
- python - 根据':'拆分txt,但不包括python中的时间戳
- javascript - 具有数据库数据的材质 UI 选择组件
- database - Redis 6 会保证客户端请求的顺序吗?
- matrix - 将此解决方案扩展到 Julia 中的高维矩阵