如何在 Java 中更改客户端 TLS 首选项?

Posted

技术标签:

【中文标题】如何在 Java 中更改客户端 TLS 首选项?【英文标题】:How to change client TLS preferences in Java? 【发布时间】: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 中的首选项,以便它仍然可以发出请求?

【问题讨论】:

【参考方案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 协议进行连接。

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

【讨论】:

以上是关于如何在 Java 中更改客户端 TLS 首选项?的主要内容,如果未能解决你的问题,请参考以下文章

如何检测首选项中是不是进行了更改?

如何根据偏好更改显示的按钮[关闭]

java 更改摘要共享首选项

当用户更改首选项时如何在运行时禁用 Crashlytics/Fabric

如何在 java 中使用 Smack XMPP 库处理 TLS 证书

在 android 首选项片段中使用 startService()