【问题标题】:How to set TLS version on apache HttpClient如何在 apache HttpClient 上设置 TLS 版本
【发布时间】:2023-04-06 20:51:01
【问题描述】:

如何更改 HttpClient 上支持的 TLS 版本?

我在做:

SSLContext sslContext = SSLContext.getInstance("TLSv1.1");
sslContext.init(
    keymanagers.toArray(new KeyManager[keymanagers.size()]),
    null,
    null);

SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, new String[]{"TLSv1.1"}, null, null);
Scheme scheme = new Scheme("https", 443, socketFactory);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(scheme);
BasicClientConnectionManager cm = new BasicClientConnectionManager(schemeRegistry);
httpClient = new DefaultHttpClient(cm);

但是当我检查创建的套接字时,它仍然说支持的协议是 TLSv1.0、TLSv1.1 和 TLSv1.2。

实际上我只是希望它停止使用 TLSv1.2,用于这个特定的 HttpClient。

【问题讨论】:

  • 您要针对哪个版本的 HttpClient?

标签: java ssl apache-httpclient-4.x


【解决方案1】:

解决办法是:

SSLContext sslContext = SSLContexts.custom()
    .useTLS()
    .build();

SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(
    sslContext,
    new String[]{"TLSv1", "TLSv1.1"},   
    null,
    BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

httpClient = HttpClients.custom()
    .setSSLSocketFactory(f)
    .build();

这需要 org.apache.httpcomponents.httpclient 4.3.x。

【讨论】:

  • 这似乎已被弃用。您能否更新以反映新的 httpClient 版本?
  • 非常适合我。我应用这个解决方案来强制调用使用 TLSv1.2,因为像 PayPay 和 UPS 的 API 这样的供应商现在正在强制执行它。 Java7 默认是 TLSv1,Java8 默认是 TLSv1.2 但是我现在不能升级 Java,所以这篇文章对我很有帮助。
  • 如果我想用 httpclient 4.1.1 做类似的事情怎么办?
  • @Oliveira 设置系统环境 https.protocols=TLSv1,TLSv1.1 没有得到相同的结果?
  • @Oliveira 当您使用HttpClientBuilder 时,这只有在您启用systemProperties 时才有效,因为它默认是关闭的。
【解决方案2】:

这就是我在 httpClient 4.5 上工作的方式(根据橄榄树请求):

CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
        new AuthScope(AuthScope.ANY_HOST, 443),
        new UsernamePasswordCredentials(this.user, this.password));

SSLContext sslContext = SSLContexts.createDefault();

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
        new String[]{"TLSv1", "TLSv1.1"},
        null,
        new NoopHostnameVerifier());

CloseableHttpClient httpclient = HttpClients.custom()
        .setDefaultCredentialsProvider(credsProvider)
        .setSSLSocketFactory(sslsf)
        .build();

return httpclient;

【讨论】:

  • 请注意,上面的代码 sn-p 使用了 NoopH​​ostnameVerifier,它实际上关闭了主机名验证。在大多数情况下,这是一件非常糟糕的事情。
  • 可以只使用 org.apache.http.conn.ssl.DefaultHostnameVerifier 吗?
【解决方案3】:

如果您使用的是 httpclient 4.2,那么您需要编写一些额外的代码。我希望能够自定义“启用 TLS 的协议”(例如 TLSv1.1,特别是 TLSv1TLSv1.2)以及 cipher suites

public class CustomizedSSLSocketFactory
    extends SSLSocketFactory
{
    private String[] _tlsProtocols;
    private String[] _tlsCipherSuites;

    public CustomizedSSLSocketFactory(SSLContext sslContext,
                                      X509HostnameVerifier hostnameVerifier,
                                      String[] tlsProtocols,
                                      String[] cipherSuites)
    {
        super(sslContext, hostnameVerifier);

        if(null != tlsProtocols)
            _tlsProtocols = tlsProtocols;
        if(null != cipherSuites)
            _tlsCipherSuites = cipherSuites;
    }

    @Override
    protected void prepareSocket(SSLSocket socket)
    {
        // Enforce client-specified protocols or cipher suites
        if(null != _tlsProtocols)
            socket.setEnabledProtocols(_tlsProtocols);

        if(null != _tlsCipherSuites)
            socket.setEnabledCipherSuites(_tlsCipherSuites);
    }
}

然后:

    SSLContext sslContext = SSLContext.getInstance("TLS");

    sslContext.init(null, getTrustManagers(), new SecureRandom());

    // NOTE: not javax.net.SSLSocketFactory
    SSLSocketFactory sf = new CustomizedSSLSocketFactory(sslContext,
                                                         null,
                                                         [TLS protocols],
                                                         [TLS cipher suites]);

    Scheme httpsScheme = new Scheme("https", 443, sf);
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(httpsScheme);

    ConnectionManager cm = new BasicClientConnectionManager(schemeRegistry);

    HttpClient client = new DefaultHttpClient(cmgr);
    ...

你可以用稍微少一点的代码来做到这一点,但我主要是从一个自定义组件中复制/粘贴,在该组件中以上面显示的方式构建对象是有意义的。

【讨论】:

  • @Skynet 该技术应该是有效的,但具体的 TLS 协议和密码套件通常由底层 JVM 规定。因此,您必须查看对 Android
  • @comeOnGetIt Java 6 大多不支持高于 TLS 1.0 (stackoverflow.com/questions/33364100/…) 的任何东西。无论如何,Java 6 应该已经死了。
【解决方案4】:

如果您的代码中有 javax.net.ssl.SSLSocket 类引用,则可以通过调用 SSLSocket.setEnabledProtocols() 来设置启用的 TLS 协议:

import javax.net.ssl.*;
import java.net.*; 
...
Socket socket = SSLSocketFactory.getDefault().createSocket();
...
if (socket instanceof SSLSocket) {
   // "TLSv1.0" gives IllegalArgumentException in Java 8
   String[] protos = {"TLSv1.2", "TLSv1.1"}
   ((SSLSocket)socket).setEnabledProtocols(protos);
}

【讨论】:

    【解决方案5】:

    HttpClient-4.5,使用TLSv1.2,你必须这样编码:

     //Set the https use TLSv1.2
    private static Registry<ConnectionSocketFactory> getRegistry() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext sslContext = SSLContexts.custom().build();
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
                new String[]{"TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        return RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslConnectionSocketFactory)
                .build();
    }
    
    public static void main(String... args) {
        try {
            //Set the https use TLSv1.2
            PoolingHttpClientConnectionManager clientConnectionManager = new PoolingHttpClientConnectionManager(getRegistry());
            clientConnectionManager.setMaxTotal(100);
            clientConnectionManager.setDefaultMaxPerRoute(20);
            HttpClient client = HttpClients.custom().setConnectionManager(clientConnectionManager).build();
            //Then you can do : client.execute(HttpGet or HttpPost);
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    

    【讨论】:

      【解决方案6】:

      对于使用 TLSv1.2 的 HttpClient-4.1,代码如下所示:

              SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
              sslContext.init(null, null, new SecureRandom());
              SSLSocketFactory sf = new SSLSocketFactory(sslContext);
              Scheme httpsScheme = new Scheme("https", 443, sf);
              SchemeRegistry schemeRegistry = new SchemeRegistry();
              schemeRegistry.register(httpsScheme);
              ClientConnectionManager cm =  new        SingleClientConnManager(schemeRegistry);
              HttpClient client = new DefaultHttpClient(cm);
      
             // Use client to make the connection and get the results.
      

      【讨论】:

        【解决方案7】:

        您可以在您的服务器上指定以下属性 -Dhttps.protocols=TLSv1.1,TLSv1.2,该属性配置 JVM 以指定在来自客户端的所有 https 连接期间应使用哪个 TLS 协议版本。

        【讨论】:

        • 不。不适用于服务器端,仅在客户端 JVM Applets / Webstart 中工作。
        • 如果你使用 apache httpclient(用 3 和 4 测试),它似乎也不能正常工作。如果实施其他解决方案,我不确定这些设置是否真的必要。
        • 是的,如果 JDK 使用 URL#openConnection() 建立连接,但这些不适用于 Apache HttpClient。问题被专门标记为 apache-httpclient-4.x
        • 这也适用于 Apache http 客户端以及 useSystemProperties()
        【解决方案8】:

        在 HttpClient 4.5.x 中使用 HttpClientBuilder 和自定义 HttpClientConnectionManager,默认为 HttpClientBuilder

        SSLConnectionSocketFactory sslConnectionSocketFactory = 
            new SSLConnectionSocketFactory(SSLContexts.createDefault(),          
                                           new String[] { "TLSv1.2" },                                            
                                           null, 
                   SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
            new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory> create()
                               .register("http",
                                         PlainConnectionSocketFactory.getSocketFactory())
                               .register("https",
                                         sslConnectionSocketFactory)
                               .build());
        
        // Customize the connection pool
        
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                                                          .setConnectionManager(poolingHttpClientConnectionManager)
                                                          .build()
        

        没有自定义HttpClientConnectionManager

        SSLConnectionSocketFactory sslConnectionSocketFactory = 
            new SSLConnectionSocketFactory(SSLContexts.createDefault(),          
                                           new String[] { "TLSv1.2" },                                            
                                           null, 
                   SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                                                          .setSSLSocketFactory(sslConnectionSocketFactory)
                                                          .build()
        

        【讨论】:

          【解决方案9】:

          使用 -Dhttps.protocols=TLSv1.2 JVM 参数对我不起作用。有效的是以下代码

          RequestConfig.Builder requestBuilder = RequestConfig.custom();
          //other configuration, for example
          requestBuilder = requestBuilder.setConnectTimeout(1000);
          
          SSLContext sslContext = SSLContextBuilder.create().useProtocol("TLSv1.2").build();
          
          HttpClientBuilder builder = HttpClientBuilder.create();
          builder.setDefaultRequestConfig(requestBuilder.build());
          builder.setProxy(new HttpHost("your.proxy.com", 3333)); //if you have proxy
          builder.setSSLContext(sslContext);
          
          HttpClient client = builder.build();
          

          使用以下 JVM 参数进行验证

          -Djavax.net.debug=all
          

          【讨论】:

          【解决方案10】:

          由于这只是隐藏在 cmets 中,因此很难找到解决方案:

          可以使用java -Dhttps.protocols=TLSv1,TLSv1.1,但你也需要使用useSystemProperties()

          client = HttpClientBuilder.create().useSystemProperties();
          

          我们现在在我们的系统中使用此设置,因为这使我们能够仅针对代码的某些用途进行设置。 在我们的例子中,我们仍然有一些 Java 7 正在运行,并且一个 API 端点不允许使用 TLSv1, 所以我们使用java -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 来启用当前的 TLS 版本。 感谢@jebeaudet 指出这个方向。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-03-21
            • 2020-12-04
            • 1970-01-01
            • 2016-10-17
            • 1970-01-01
            相关资源
            最近更新 更多