最近有客户反馈java调用报错Received fatal alert: protocol_version,直接联想到是最近调整的WAF的TLS配置,取消了TLS 1.0所致。
取消TLS1.0主要是以下原因:
TLS 1.0 于 2021 年 3 月正式弃用。 此外,TLS 1.0 在用于保护传入或传出网站的敏感信息时,不被视为 PCI 数据安全标准 3.2(.1) 所定义和要求的”强加密”。 根据PCI的说法,“2018年6月30日是禁用SSL /早期TLS和实施更安全的加密协议的最后期限 - TLS 1.1或更高版本(强烈建议TLS v1.2),以满足PCI数据安全标准(PCI DSS)保护支付数据。
问题分析
大部分客户没问题,但部分客户有问题,结合以往经验,很快得知跟客户的代码环境有关。经查阅资料可知:JDK8默认使用的是TLS v1.2;而JDK7和JDK6默认使用的是TLS v1.0,需要代码进行特别指定版本号。以下是各JDK版本对TLS协议的支持对比信息:
获得域名网站所支持的ssl协议方法:
https://tech.hiofd.com/p/get-domain-ssl-protocols/
—
解决方案
解决方案1
没什么特殊情况下,解决Received fatal alert: protocol_version问题,直接升级到JDK8即可解决。
解决方案2
修改JDK默认协议,参考以下代码:
使用HttpClient的:
private static CloseableHttpClient getHttpclient(){
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(null, null, null);
SSLContext.setDefault(context);
...
}
使用RestTemplate的:
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// 强制使用 TLSv1.2 解决 Received fatal alert: protocol_version; nested exception is javax.net.ssl.SSLException: Received fatal alert: protocol_version 异常情况
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustAllCerts, new SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* SSLSocketFactory用于创建 SSLSockets
*/
private class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
// 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
// 这些默认的服务的最低质量要求保密保护和服务器身份验证
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
// 返回的密码套件可用于SSL连接启用的名字
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
// 兼容支持协议版本
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
return socket;
}
}
}