java1.7请求https接口报协议版本错误

2022-07-07 21:15:26

记录 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 则会出现协议不匹配的错误导致握手失败,从而无法正常发出请求。

参考资料

本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 许可协议。可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

扫描下方二维码阅读当前文章

浏览器、微信扫码

评 论:

好文推荐
每天进步一点点~