【问题标题】:How to change client TLS preferences in Java?如何在 Java 中更改客户端 TLS 首选项?
【发布时间】:2020-03-02 05:49:21
【问题描述】:

我正在尝试向 Java 中的端点发出 POST 请求,当我尝试发送请求时,我收到以下错误:

Caused by: javax.net.ssl.SSLHandshakeException: The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]

这是我目前所拥有的

Map<Object, Object> data = new HashMap<>();
data.put("username","foo");
data.put("password","bar");

String url = "https://google.com";

HttpRequest request = HttpRequest.newBuilder()
     .POST(buildFormDataFromMap(data))
     .uri(URI.create(url))
     .build();

try{
     HttpResponse<String> response =  httpClient.send(request, 
          HttpResponse.BodyHandlers.ofString());
     System.out.println(response.statusCode());
     System.out.println(response.body());
} catch (Exception e){
     e.printStackTrace();
}

然后,当我运行代码时,在发送请求/制作响应对象时会引发错误。我的问题是,如果服务器的 TLS 首选项与客户端不同,我该如何更改 Java 中的首选项,以便它仍然可以发出请求?

【问题讨论】:

    标签: tls1.2 sslhandshakeexception tls1.0


    【解决方案1】:

    为了在 jdk 11 中解决这个问题,我必须创建一个 javax.net.ssl.SSLParameters 对象来启用“TLSv1”等:

    SSLParameters sslParameters = new SSLParameters();
            sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
    

    然后创建 HttpClient 并添加 sslParamters 对象:

    HttpClient httpClient = HttpClient.newBuilder()
                .sslParameters(sslParameters)
                .build();
    

    如果您还想禁用主机名验证,请在 HttpClient 初始化之前添加以下代码;

    final Properties props = System.getProperties(); 
    props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
    

    您还可以添加一个新的 TrustManager 来信任所有证书(自签名)。 为此,请将以下代码添加到您的类中:

        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {     
                public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
                    return new X509Certificate[0];
                } 
                public void checkClientTrusted( 
                    java.security.cert.X509Certificate[] certs, String authType) {
                    } 
                public void checkServerTrusted( 
                    java.security.cert.X509Certificate[] certs, String authType) {
                }
            } 
        }; 
    

    在此之后,您必须创建一个 SSLContext 对象并添加 TrustManger 对象:

    SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
    

    最后像这样改变 HttpClient 初始化:

    httpClient = HttpClient.newBuilder()
                .sslContext(sslContext)
                .sslParameters(sslParameters)
                .build()
    

    这是一个完整的类示例:

    import java.net.http.HttpClient;
    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.X509Certificate;
    import java.util.Properties;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLParameters;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    public class HttpSSLClient {
    
        private SSLContext sslContext;
        private SSLParameters sslParameters;
        private HttpClient httpClient;
    
        public HttpSSLClient() throws KeyManagementException, NoSuchAlgorithmException {
    
            sslParameters = new SSLParameters();
            sslParameters.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
    
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
    
            final Properties props = System.getProperties(); 
            props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
    
            httpClient = HttpClient.newBuilder()
                    .sslContext(sslContext)
                    .sslParameters(sslParameters)
                    .build();
        }
    
        public HttpClient getHttplClient() {
            return httpClient;
        }
    
        TrustManager[] trustAllCerts = new TrustManager[] { 
                new X509TrustManager() {     
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
                        return new X509Certificate[0];
                    } 
                    public void checkClientTrusted( 
                        java.security.cert.X509Certificate[] certs, String authType) {
                        } 
                    public void checkServerTrusted( 
                        java.security.cert.X509Certificate[] certs, String authType) {
                    }
                } 
            }; 
    }
    

    您可以在调用 HttpRequest 时使用 getHttplClient() 函数。

    【讨论】:

    • 非常有帮助!感谢分享!
    • 请注意,此答案包含的代码比问题所要求的要多得多。阅读本文的人可能只需要.setProtocols(...) 部分和使用它来构建HttpClient 的代码...
    • 我完成了上述所有操作,但我仍然收到IOException: The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12] caused by: SSLHandshakeException: The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]。一定还是少了点什么。这是在 Java 11 (AdoptOpenJDK) 上。
    • 刚刚检测到仍然缺少的内容:在文件 /conf/security/java.security 中,需要从disabledAlgorithms-list 中删除“TLSv1”和“TLSv1.1”。也可以在运行时使用如下代码执行此操作:String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms"); Security.setProperty("jdk.tls.disabledAlgorithms", disabledAlgorithms .replace("TLSv1,", ""));
    【解决方案2】:

    我遇到了同样的问题,这个解决方案对我不起作用。 相反,我看到了这个答案Android Enable TLSv1.2 in OKHttp,我尝试了这段代码: ConnectionSpec 规范 = 新的 ConnectionSpec .Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2,TlsVersion.TLS_1_0,TlsVersion.TLS_1_1,TlsVersion.TLS_1_3).build(); 客户端 =client.newBuilder().connectionSpecs(Collections.singletonList(spec)).build(); 它对我有用:)

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    • 这并不能真正回答问题。如果您有其他问题,可以点击 进行提问。要在此问题有新答案时收到通知,您可以follow this question。一旦你有足够的reputation,你也可以add a bounty 来引起对这个问题的更多关注。 - From Review
    【解决方案3】:

    我认为 mmo 的答案应该以粗体突出显示。我有类似的问题,但发现我使用的 open-jdk jvm 在 java.security 的 jdk.tls.disabledAlgorithms 行中禁用了 TLSv1 和 TLSv1.1。因此,只要我删除它并重新启动 JVM,我就能够使用旧的 TLS 协议进行连接。

    但是请注意,这在生产中是不可取的,因为它会降低安全通信。所以我会说如果你想改变它,你自己承担风险!!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-01
      • 2015-11-30
      • 2021-08-11
      • 1970-01-01
      • 1970-01-01
      • 2011-02-17
      相关资源
      最近更新 更多