Contents

在Apache HttpClient中设置TLS版本

1.简介

Apache HttpClient 是一个用于与 HTTP 服务器通信的低级、轻量级客户端 HTTP 库。在本教程中,我们将学习如何在使用HttpClient 时配置支持的传输层安全 (TLS) 版本。我们将从概述客户端和服务器之间的 TLS 版本协商如何工作开始。之后,我们将了解在使用 HttpClient 时配置支持的 TLS 版本的三种不同方式

2.TLS版本协商

TLS 是一种互联网协议,可在两方之间提供安全、可信的通信。它封装了 HTTP 等应用层协议。自 1999 年首次发布以来,TLS 协议已经进行了多次修订。**因此,客户端和服务器首先就建立新连接时将使用哪个版本的 TLS 达成一致非常重要。**客户端和服务器交换 hello 消息后就 TLS 版本达成一致:

  1. 客户端发送支持的 TLS 版本列表。
  2. 服务器选择一个并在响应中包含所选版本。
  3. 客户端和服务器使用所选版本继续连接设置。

由于存在降级攻击 的风险,正确配置 Web 客户端支持的 TLS 版本非常重要。请注意,为了使用最新版本的 TLS (TLS 1.3),我们必须使用 Java 11 或更高版本。

3. 静态设置 TLS 版本

3.1. SSLConnectionSocketFactory

让我们使用HttpClients#custom builder方法公开的 HttpClientBuilder 来定义我们的HTTPClient配置。此构建器模式允许我们传入我们自己的SSLConnectionSocketFactory,它将使用所需的支持 TLS 版本集进行实例化:

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
  SSLContexts.createDefault(),
  new String[] { "TLSv1.2", "TLSv1.3" },
  null,
  SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

返回的Httpclient对象现在可以执行 HTTP 请求。通过在SSLConnectionSocketFactory构造函数中显式设置支持的协议,客户端将仅支持通过 TLS 1.2 或 TLS 1.3 进行通信。请注意,在 4.3 之前的 Apache HttpClient 版本中,该类称为SSLSocketFactory

3.2. Java 运行时参数

或者,我们可以使用 Java 的https.protocols系统属性配置支持的 TLS 版本。这种方法可以避免将值硬编码到应用程序代码中。相反,我们将配置HttpClient以在设置连接时使用系统属性。HttpClient API 提供了两种方法来做到这一点。第一个是通过HttpClients#createSystem

CloseableHttpClient httpClient = HttpClients.createSystem();

如果需要更多的客户端配置,我们可以使用 builder 方法来代替:

CloseableHttpClient httpClient = HttpClients.custom().useSystemProperties().build();

这两种方法都告诉HttpClient在连接配置期间使用系统属性。这允许我们在应用程序运行时使用命令行参数设置所需的 TLS 版本。例如:

$ java -Dhttps.protocols=TLSv1.1,TLSv1.2,TLSv1.3 -jar webClient.jar

4.动态设置TLS版本

还可以根据主机名和端口等连接详细信息设置 TLS 版本。我们将扩展SSLConnectionSocketFactory并覆盖prepareSocket方法。客户端在发起新连接之前调用*prepareSocket方法。***这将让我们决定在每个连接的基础上使用哪些 TLS 协议。**也可以启用对旧 TLS 版本的支持,但前提是远程主机具有特定的子域:

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(SSLContexts.createDefault()){
    @Override
    protected void prepareSocket(SSLSocket socket) {
        String hostname = socket.getInetAddress().getHostName();
        if (hostname.endsWith("internal.system.com")){
            socket.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" });
        }
        else {
            socket.setEnabledProtocols(new String[] {"TLSv1.3"});
        }
    }
};<br />
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

在上面的示例中,prepareSocket方法首先获取SSLSocket将连接到的远程主机名。**然后使用主机名来确定要启用的 TLS 协议。**现在,我们的 HTTP 客户端将对每个请求强制执行 TLS 1.3,除非目标主机名的格式为 *.internal.example.com。通过在创建新SSLSocket之前插入自定义逻辑的能力,我们的应用程序现在可以自定义 TLS 通信细节。