英文:
MTLS and http client connection pool usage
问题
背景
- 我需要为多个客户端连接到服务器。
- 每个客户端连接应使用唯一的TLS证书。
- 服务器上已经实施了MTLS(双向TLS认证)。
- 我希望使用连接池来提高延迟。
使用以下HTTP客户端库:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
我的假设
在管理连接池中的连接时,选择连接池中的连接之前应考虑客户端证书。我不希望为clientA的连接使用clientB的TLS证书,反之亦然。
问题
这个假设是否正确?
场景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来选择要使用的客户端证书。
如何强制客户端连接不重用会话,以便于我们打算使用不同客户端证书的连接?
客户端日志
链接到日志
英文:
Background
- I am required to connect to a server for various clients.
- Each client connection should use a unique TLS cert.
- MTLS is in place on the server.
- I want to use connection pooling to improve latency.
Using the following http client
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
My Assumption
When managing connections in the connection pool, when a connection is being selected the client certificate should be considered before selecting the same connection in the connection pool.
I do not want connections for clientA using clientB TLS cert and vice versa.
Question
Is this assumption true?
Scenario 1)
I have max connections per route set to 2.
I make a call to the MTLS secured server for client A. (one connection in the pool)
I make a call to the MTLS secured server for client A. (two connections in the pool)
Should this not of reused the first connection?
Scenario 2)
I have max connections per route set to 2.
I make a call to the MTLS secured server for client A. (one connection in the pool)
I make a call to the MTLS secured server for client B. (two connections in the pool)
However the second call does not seem to carry out a full handshake an is using clientA certificate.
I would expect the second call to require a full handshake and the connections to be not related in anyway.
Is this the expected behaviour? Am I missing something obvious here?
Updated simpler test case
We are now using the http context so I have attached the updated logs.
I have simplified the test case too and now it connects to the same server twice each time it should use a different client certificate.
The application is using spring boot and has a single restTemplate and single httpClient.
Its using a PrivateKeyStrategy to decide what private key/certificate to use when communicating with the server.
The first connection uses key alias 'e2e_transport_key_id_franek' (you will see this in the logs)
The second connection should use alias 'e2e_transport_key_id_pdw' (never seen in the logs)
The second connection we are making should use the key/cert with alias 'e2e_transport_key_id_pdw' however the session is resumed see line 448 Try resuming session. Which means we cannot use the PrivateKeyStrategy to pick the client certificate to use.
How to force the client connection to not reuse the session for connections we intend to use a different client certificate for?
client logs
https://pastebin.com/zN0EW3Qy
答案1
得分: 5
> 这个假设是真实的吗?
你的假设是正确的。一个连接池用于请求和释放连接的方法都会接受一个名为 state 的额外参数。这个 state 参数通常会带一个用户令牌,如果没有进行身份验证,则为 <code>null</code>。
只有当使用与将连接释放回连接池时所用的相同用户令牌来请求连接时,才能重新使用连接。
这种机制也适用于 SSL 客户端证书。在成功进行 SSL 握手后,将释放一个带有表示用户令牌的 X500Principal 的 SSL 连接。此令牌也存储在用于请求的 <code>HttpContext</code> 对象中。要在随后的 HTTP 请求中重新使用已释放的连接,您还必须重新使用第一个 HTTP 请求的 <code>HttpContext</code>。
> 场景 1
clientA.execute(new HttpGet("..."));
clientA.execute(new HttpGet("..."));
第一个请求触发完整的 SSL 握手。连接与用户令牌一起被释放。第二个请求使用一个新的 <code>HttpContext</code>,其中不包含任何用户令牌。因此,连接池中的连接无法重新使用,并且将创建一个带有完整握手的新连接。
> 场景 2
HttpContext context = new HttpClientContext();
clientA.execute(new HttpGet("..."), context);
clientB.execute(new HttpGet("..."), context);
如前所述,用户令牌存储在 <code>HttpContext</code> 中。因此,如果您重新使用第一个请求的 <code>HttpContext</code>,第二个请求可以重新使用已存在的连接,即使您使用了具有不同连接工厂/客户端证书的不同客户端。如果应该使用连接,则会使用 clientB 的连接工厂创建新连接。
为了区分 <code>clientA</code> 和 <code>clientB</code>,并确保每个客户端只重新使用连接,您需要为每个客户端使用一个 <code>HttpContext</code>,并且为每个请求重复使用上下文:
HttpContext contextA = new HttpClientContext();
clientA.execute(new HttpGet("..."), contextA);
clientA.execute(new HttpGet("..."), contextA);
HttpContext contextB = new HttpClientContext();
clientB.execute(new HttpGet("..."), contextB);
请注意,如果会话超时或请求重新协商,则在重新使用连接时可能仍需要进行完整的握手。
英文:
> Is this assumption true?
Your assumption is right. The methods of a connection pool for requesting and releasing a connection take both an additional argument with the name state. This state argument usually takes a user token or <code>null</code> if no authentication is in place.
A connection can only be re-used when it is requested with the same user token that was used to release the connection back into the connection pool.
This mechanism works also for SSL client certificates. After a successful SSL handshake an SSL connection is released together with an X500Principal representing the user token. This token is also stored in the <code>HttpContext</code> object that is used for the request. To re-use the released connection in subsequent HTTP requests you have also to re-use the <code>HttpContext</code> of the first HTTP request.
> Scenario 1
clientA.execute(new HttpGet("..."));
clientA.execute(new HttpGet("..."));
The first request triggers a full SSL handshake. The connection is released with the user token. The second request uses a new <code>HttpContext</code> which does not contain any user token. Therefore the connection within the pool cannot be re-used and a new connection is created with a full handshake.
> Scenario 2
HttpContext context = new HttpClientContext();
clientA.execute(new HttpGet("..."), context);
clientB.execute(new HttpGet("..."), context);
As explained before, the user token is stored within the <code>HttpContext</code>. So if you re-use the <code>HttpContext</code> from the first request the second request can re-use the already existing connection even when you use a different client with a different connection factory / client certificate. If the connection should be in use a new connection would be created with the connection factory of clientB.
To separate <code>clientA</code> and <code>clientB</code> and to ensure that connections are only re-used per client, you have to use one <code>HttpContext</code> per client and re-use the context for every request:
HttpContext contextA = new HttpClientContext();
clientA.execute(new HttpGet("..."), contextA);
clientA.execute(new HttpGet("..."), contextA);
HttpContext contextB = new HttpClientContext();
clientB.execute(new HttpGet("..."), contextB);
Please note that in case of a session timeout or a requested renegotiation a full handshake may also be required even when a connection gets reused.
答案2
得分: 2
> 这个假设是否正确?
你的假设是正确的。HttpClient 4和5能够跟踪与HTTP连接相关的用户特定状态(NTLM上下文、TLS用户身份等),并在重用持久连接时考虑到这一点。
> 场景1)
只要它与前者共享相同的执行上下文,后者的调用应该重用现有的连接。
> 场景2)
不是的。请提供会出现问题的会话的完整上下文/线路日志,我将尝试找出问题的原因。
http://hc.apache.org/httpcomponents-client-4.5.x/logging.html
英文:
> Is this assumption true?
Your assumption is correct. HttpClient 4 and 5 are capable of tracking user specific state associated with HTTP connections (NTLM context, TLS user identity, etc) and take it into account when re-using persistent connections.
> Scenario 1)
The latter call should re-use the existing connection, as long as it shares the same execution context with the former.
> Scenario 2)
No, it is not. Please provide a complete context / wire log of the session exhibiting the problem and I will try to figure out the reason.
http://hc.apache.org/httpcomponents-client-4.5.x/logging.html
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论