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