【问题标题】:MTLS and http client connection pool usageMTLS 和 http 客户端连接池使用情况
【发布时间】:2020-12-28 21:57:24
【问题描述】:

背景

  • 我需要为各种客户端连接到服务器。
  • 每个客户端连接都应使用唯一的 TLS 证书。
  • MTLS 在服务器上就位。
  • 我想使用连接池来改善延迟。

使用如下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)

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

但是,第二次调用似乎没有执行完整的握手,而是使用了 clientA 证书。

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

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

更新了更简单的测试用例

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

应用程序正在使用 spring boot,并且有一个 restTemplate 和一个 httpClient。

它使用 PrivateKeyStrategy 来决定在与服务器通信时使用什么私钥/证书。

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

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

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

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

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

【问题讨论】:

  • 我可能不太明白您遇到的问题。我可以清楚地看到所有三个请求的新连接请求和完整的 TLS 握手。
  • 我在问题中添加了额外的细节,谢谢
  • 我清楚地看到了三个不同的连接和三个不同的 TLS 握手。很抱歉,我不明白您遇到的问题。是关于第三次消息交换没有重新使用第一次的连接吗?
  • PoolingHttpClientConnectionManager - Connection request: [route: {s}-&gt;https://server:8443][total available: 2; route allocated: 2 of 2; total allocated: 2 of 20] 第三个请求不包含状态(用户令牌),所以HttpClient不重用现有的持久有状态连接是绝对正确的。
  • 您设置 TLS 上下文的方式有问题。您很可能需要使用不同的 TLS 会话缓存来发出不同的请求,或者使用 SSLSession#invalidate() 手动使 TLS 会话无效以防止它们恢复。

标签: java ssl tls1.2 apache-httpclient-4.x mtls


【解决方案1】:

这个假设是真的吗?

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

场景 1)

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

场景 2)

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

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

【讨论】:

  • 我已按照@ok2c 的要求附上了日志。在这里感谢您提供的帮助。
【解决方案2】:

这个假设是真的吗?

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

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

此机制也适用于 SSL 客户端证书。成功的 SSL 握手后,SSL 连接与代表用户令牌的 X500Principal 一起被释放。此令牌也存储在用于请求的HttpContext 对象中。要在后续 HTTP 请求中重新使用已释放的连接,您还必须重新使用第一个 HTTP 请求的HttpContext

场景 1

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

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

场景 2

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

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

要分隔 clientAclientB 并确保每个客户端仅重复使用连接,您必须为每个客户端使用一个 HttpContext 并为每个请求重复使用上下文:

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

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

请注意,如果会话超时或请求重新协商,即使连接被重用,也可能需要完全握手。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-10
    • 2020-05-16
    • 1970-01-01
    • 2017-08-04
    • 2012-06-13
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多