如何在 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) 上。
刚刚检测到仍然缺少的内容:在文件 disabledAlgorithms
-list 中删除“TLSv1”和“TLSv1.1”。也可以在运行时使用如下代码执行此操作:String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms"); Security.setProperty("jdk.tls.disabledAlgorithms", disabledAlgorithms .replace("TLSv1,", ""));
我遇到了同样的问题,这个解决方案对我不起作用。 相反,我看到了这个答案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 首选项?的主要内容,如果未能解决你的问题,请参考以下文章
当用户更改首选项时如何在运行时禁用 Crashlytics/Fabric