MTLS和http客户端连接池使用

MTLS and http client connection pool usage

背景

使用以下 http 客户端

<dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
   <version>4.5.12</version>
</dependency>

我的假设 在连接池中管理连接时,在选择连接时,应在连接池中选择相同连接之前考虑客户端证书。 我不想使用 clientB TLS 证书连接 clientA,反之亦然。

问题 这个假设是真的吗?

场景 1)

我将每个路由的最大连接数设置为 2。 我为客户端 A 调用 MTLS 安全服务器。(池中的一个连接) 我为客户端 A 调用 MTLS 安全服务器。(池中的两个连接) 这不应该重用第一个连接吗?

场景二)

我将每个路由的最大连接数设置为 2。 我为客户端 A 调用 MTLS 安全服务器。(池中的一个连接) 我为客户端 B 调用了 MTLS 安全服务器。(池中的两个连接)

然而,第二次调用似乎没有执行完整的握手,并且正在使用 clientA 证书。

我希望第二次调用需要完全握手并且无论如何连接都不相关。

这是预期的行为吗?我在这里遗漏了一些明显的东西吗?

更新了更简单的测试用例

我们现在使用的是 http 上下文,所以我附上了更新后的日志。 我也简化了测试用例,现在它每次都连接到同一台服务器两次,应该使用不同的客户端证书。

该应用程序正在使用 spring 启动并且只有一个 restTemplate 和一个 httpClient。

它使用 PrivateKeyStrategy 来决定在与服务器通信时使用什么私有key/certificate。

第一个连接使用密钥别名'e2e_transport_key_id_franek'(您将在日志中看到)

第二个连接应该使用别名 'e2e_transport_key_id_pdw'(在日志中从未见过)

我们正在建立的第二个连接应该使用别名 'e2e_transport_key_id_pdw' 的 key/cert 但是会话已恢复,请参阅第 448 行尝试恢复会话。这意味着我们不能使用 PrivateKeyStrategy 来选择要使用的客户端证书。

如何强制客户端连接不重用我们打算使用不同客户端证书的连接的会话?

客户端日志 https://pastebin.com/zN0EW3Qy

Is this assumption true?

你的假设是正确的。 HttpClient 4 和 5 能够跟踪与 HTTP 连接相关的用户特定状态(NTLM 上下文、TLS 用户身份等),并在 re-using 持久连接时将其考虑在内。

Scenario 1)

后一个调用应该 re-use 现有连接,只要它与前一个调用共享相同的执行上下文。

Scenario 2)

不,不是。请提供出现问题的会话的 完整 上下文/线路日志,我将尝试找出原因。

http://hc.apache.org/httpcomponents-client-4.5.x/logging.html

Is this assumption true?

你的假设是正确的。 requesting and releasing 连接的连接池方法采用名称为 state 的附加参数。此 state 参数通常采用用户令牌或 null 如果没有进行身份验证。

只有在使用用于将连接释放回连接池的相同用户令牌请求连接时,连接才能 re-used。

此机制也适用于 SSL 客户端证书。成功的 SSL 握手后,将释放 SSL 连接以及代表用户令牌的 X500Principal。此令牌还存储在用于请求的 HttpContext 对象中。要 re-use 在后续 HTTP 请求中释放连接,您还必须 re-use 第一个 HTTP 请求的 HttpContext

Scenario 1

clientA.execute(new HttpGet("..."));
clientA.execute(new HttpGet("..."));

第一个请求触发完整的 SSL 握手。连接随用户令牌一起释放。第二个请求使用不包含任何用户令牌的新 HttpContext。因此,池中的连接不能 re-used 并且使用完全握手创建新连接。

Scenario 2

HttpContext context = new HttpClientContext();
clientA.execute(new HttpGet("..."), context);
clientB.execute(new HttpGet("..."), context);

如前所述,用户令牌存储在 HttpContext 中。因此,如果您 re-use 来自第一个请求的 HttpContext 第二个请求可以 re-use 已经存在的连接,即使您使用具有不同连接工厂/客户端证书的不同客户端也是如此。如果连接应该在使用中,将使用 clientB 的连接工厂创建一个新连接。

要分开 clientAclientB 并确保每个客户端只有 re-used 个连接,您必须为每个客户端使用一个 HttpContext 和 re-use 每个请求的上下文:

HttpContext contextA = new HttpClientContext();
clientA.execute(new HttpGet("..."), contextA);
clientA.execute(new HttpGet("..."), contextA);

HttpContext contextB = new HttpClientContext();
clientB.execute(new HttpGet("..."), contextB);

请注意,在会话超时或请求重新协商的情况下,即使重新使用连接,也可能需要完全握手。