记录 java 请求接口报 Received fatal alert: protocol_version)错误的处理方法
# 背景
最近公司项目接到合作方反馈某接口出现调不通的问题,错误信息:
Received fatal alert: protocol_version
发现是客户端与服务端 tls 协议版本不一致导致。
服务器使用的是 1.7 版本的 Java,考虑到做 jdk 整体版本升级风险会比较大,所以选择在代码层做绕过。
# 解决方法
在代码中增加绕过的操作,新建如下三个文件:
HttpTest
测试类:
package httpClient;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class HttpTest {
public static void main(String[] args) {
HttpURLConnection con = null;
BufferedReader buffer = null;
StringBuffer resultBuffer = null;
try {
URL url = new URL("https://xxx.com/xx");
// 得到连接对象
con = (HttpURLConnection) url.openConnection();
// 设置请求类型
con.setRequestMethod("POST");
// 设置Content-Type,此处根据实际情况确定
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (con instanceof HttpsURLConnection) {
// 注释掉之后java1.7会报错
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
((HttpsURLConnection) con).setSSLSocketFactory(sc.getSocketFactory());
((HttpsURLConnection) con).setHostnameVerifier(new TrustAnyHostnameVerifier());
}
// 允许写出
con.setDoOutput(true);
// 允许读入
con.setDoInput(true);
// 不使用缓存
con.setUseCaches(false);
OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream(), "UTF-8");
out.append("code=ccc");
out.flush();
// 得到响应码
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 得到响应流
InputStream inputStream = con.getInputStream();
// 将响应流转换成字符串
resultBuffer = new StringBuffer();
String line;
buffer = new BufferedReader(new InputStreamReader(inputStream, "GBK"));
while ((line = buffer.readLine()) != null) {
resultBuffer.append(line);
}
System.out.println("result:" + resultBuffer.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TrustAnyHostnameVerifier
类文件:
package httpClient;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
public class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
// 直接Pass
return true;
}
}
TrustAnyTrustManager
类文件:
package httpClient;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
}
# 测试
测试命令,分别用 jdk 1.7、1.8 进行测试:
// 编译
javac httpClient/HttpTest.java
// 运行
/usr/bin/env /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/bin/java httpClient/HttpTest
/usr/bin/env /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java httpClient/HttpTest
如果使用 1.7 则会出现如下结果:
javax.net.ssl.SSLException: Received fatal alert: protocol_version
at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1979)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1086)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1092)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
at httpClient.HttpTest.main(HttpTest.java:54)
# 原因分析
1.7 默认使用的 1.1 tls
协议进行握手连接,服务端如果使用 1.2 的tls
则会出现协议不匹配的错误导致握手失败,从而无法正常发出请求。