Received fatal alert: handshake_failure

背景

从后端请求第三方的提供的https接口,一直提示握手失败javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure,但api放到浏览器直接访问是没问题的,这也证明了人家提供的api可用性。

我发起请求的客户端是jdk8版本,我试着请求其他平台https接口,能获取到数据,又证明了我代码是没问题了。那问题出在哪?

分析

我在本地的IDEA打开SSL的debug调试,在jvm启动加入 

-Djavax.net.debug=all
发起一个请求,携带版本号,加密套件列表,发起第一次握手

 按正常流程,服务器端应该给我回礼Hello,但在日志中却看到握手失败的提示

 这说明服务器那边验证没通过,直接拒绝了本次握手,我们打开分析工具https://myssl.com/,分析下请求的域名

 对方的协议是TLS1.3而我发起的客户端基于jdk8发起的,以下是jdk各版本支持的协议

 jdk8版本并没有提供TLSv1.3的协议支持,所以需要额外的openjsse依赖 可参考该博主 https://blog.csdn.net/devzyh/article/details/122074632

解决

我maven引入该依赖包

    <dependency>
            <groupId>org.openjsse</groupId>
            <artifactId>openjsse</artifactId>
            <version>1.1.13</version>
        </dependency>

对方的加密套件是 TLS_AES_256_GCM_SHA384索性就把我的jdk8小版本升级一波(注意:如果jdk版本比较新,以上问题都不存在,我试过jdk17能正常访问)但若项目不允许大版本升级,那就老老实实的加依赖包吧

改完代码,我们重新发起一个请求

    public static String sendHttpsGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        // 支持TLSv1.3协议的依赖注册到提供者中
        Security.addProvider(new OpenJSSE());
        // 指定请求的协议版本
        SSLContext sslcontext = null;
        try {
            sslcontext = SSLContext.getInstance("TLSv1.3");

            X509TrustManager x509TrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            sslcontext.init(null, new TrustManager[]{x509TrustManager}, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());

            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36");
            // 建立实际的连接
            connection.connect();

            System.out.println("连接成功");
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            /*
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            */
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

查看日志,成功握手,并获取到调用的api接口信息

 若出现提示证书问题,那就需要导入证书,可参考该博文的keytool操作 https://blog.csdn.net/HD243608836/article/details/118705725

posted @ 2024-01-31 17:44  一剑天门  阅读(145)  评论(0编辑  收藏  举报