问题背景
1、老的项目,正式环境为Linux环境,使用IBM-jdk,因使用的用户不多,因此客户方没有使用WAS(没有购买WAS)
2、后续扩展开发接口需要调用第三方系统的https接口,2020年3月上线时,该接口可以正常调用第三方的https接口服务;2020年6月第三方系统的证书过期了,后来更换了新的厂商的安全证书,导致原来的接口无法调用第三方的https接口服务。报错为:javax.net.ssl.SSLException: Received fatal alert: protocol_version。后来试过在正式环境新加了一个1.0协议的链路,使用该链路可以正常调通第三方的https接口,但是该方法只能临时解决。
老项目的jdk版本比较旧:
java version "1.7.0"
Java(TM) SE Runtime Environment (build pxa6470sr4fp1ifix-20130423_02(SR4 FP1+IV38579+IV38399+IV40208))
IBM J9 VM (build 2.6, JRE 1.7.0 Linux amd64-64 Compressed References 20130421_145945 (JIT enabled, AOT enabled)
J9VM - R26_Java726_SR4_FP1_2_20130421_2353_B145945
JIT - r11.b03_20130131_32403ifx4
GC - R26_Java726_SR4_FP1_2_20130421_2353_B145945_CMPRSS
J9CL - 20130422_145945)
JCL - 20130225_01 based on Oracle 7u13-b08
在网上找过各种资料,试过调整httpclient客户端发起请求的代码,将第三方系统协议版本1.2直接写在代码里面,报错javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure,这个错基本说明改客户端代码的协议版本到1.2的思路是对的,但是仍旧失败,加了各种常见的jvm参数也仍旧失败。加的参数有
-Djdk.tls.client.protocols=TLSv1.2 -Dhttps.protocols=TLSv1.2 -Ddeployment.security.SSLv2Hello=false -Ddeployment.security.SSLv3=false -Ddeployment.security.TLSv1=false -Ddeployment.security.TLSv1.1=false -Ddeployment.security.TLSv1.2=true
看过IBM官网:https://www.ibm.com/support/pages/node/254939
里面有一段这么描述的:
也就是说,ibm1.7的jdk,作为客户端发送请求,仅支持1.0的协议,对于更高版本的1.2的协议是不支持。
而且我这边出现了官网类似的一段报错:
且官网这里并没有给出解决办法,所以该问题在IBM1.7版的jdk下无法解决(特殊说明,有项目也是使用的该版本jdk,后面解决了是因为他们用了IBM配套的was中间件,有专门的解决方案可以解决该问题)
解决方案
1、更换为oracle的1.7的jdk,版本为jdk1.7.0_51,使用上面的代码,仍旧报错,也是上面握手失败的问题。后来试了jdk1.7.0_80版本的jdk也不行。
2、我将httpclient端的代码单独拿出来,写了一个main方法单独测试,并加上jvm调试参数-Djavax.net.debug=all,单独运行后有一段重要的解决如下:
上面的日志说明写/读都是1.2的协议,但是RECV用的TLSv1,说明光改代码没有用。
3、后来查资料,也找同事问了,可能这个问题需要升级jdk的小版本(虽然直接升级到1.8能解决,但是产品底层用的1.7,但是直接升级到1.8系统没法运行,所以直接在小版本内升级),官网说明如下:https://community.oracle.com/tech/developers/discussion/3965168/how-to-enable-tlsv1-2-in-java-1-7
https://www.java.com/en/configure_crypto.html#enableTLSv1_2
(感谢帮忙在网上各种找资料的小哥哥)
4、后来升级了jdk版本到1.7的最后一个版本jdk-7u191,这个问题终于解决。
main, WRITE: TLSv1.2 Application Data, length = 235
[Raw write]: length = 240
0000: 17 03 03 00 EB 00 00 00 00 00 00 00 01 35 80 C6 .............5..
0010: F1 4E 6D 4F 0B 91 5B 30 4F DC B8 3B 0A 9F 32 56 .NmO..[0O..;..2V
......(此处省略一部分日志)
[Raw read]: length = 5
0000: 17 03 03 01 88 .....
[Raw read]: length = 392
0000: 99 1A E3 1F 93 6F 79 CC 43 13 04 0A 3F B5 6A 88 .....oy.C...?.j.
0010: F5 FA 02 EF 15 C4 5A A5 57 2B 67 A6 80 C6 B7 64 ......Z.W+g....d
......(此处省略一部分日志)
main, READ: TLSv1.2 Application Data, length = 392
Padded plaintext after DECRYPTION: len = 368
0000: 48 54 54 50 2F 31 2E 31 20 34 30 31 20 55 6E 61 HTTP/1.1 401 Una
0010: 75 74 68 6F 72 69 7A 65 64 0D 0A 44 61 74 65 3A uthorized..Date:
......(此处省略一部分日志)
HTTP/1.1 401 Unauthorized
{"errorCode":2,"msg":"Authorization Failed"}--(这里已经正常报错返回了,不再是握手失败)
main, called close()
main, called closeInternal(true)
main, SEND TLSv1.2 ALERT: warning, description = close_notify
Padded plaintext before ENCRYPTION: len = 2
0000: 01 00 ..
main, WRITE: TLSv1.2 Alert, length = 26
[Raw write]: length = 31
0000: 15 03 03 00 1A 00 00 00 00 00 00 00 02 E9 D1 1D ................
0010: 21 E2 47 01 A0 43 90 0E 6F 4E D2 C4 DE 14 BB !.G..C..oN.....
main, called closeSocket(selfInitiated)
测试代码(仅供参考)
public static void main(String args[]) throws FileNotFoundException {
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());//RSA PKCS12
} catch (KeyStoreException e) {
System.err.println("获取证书实例异常");
e.printStackTrace();
return;
}
try {
//加载证书,需要附上证书的密码
keyStore.load(new FileInputStream(new File("/XXXXXX.jks")), "123456".toCharArray());
} catch (NoSuchAlgorithmException | CertificateException
| IOException e) {
System.err.println("加载证书异常");
e.printStackTrace();
return;
}
SSLContext sslcontext = null;
try {
sslcontext = SSLContexts.custom()
//忽略掉对服务器端证书的校验
.loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
})
.loadKeyMaterial(keyStore, "123456".toCharArray())
.build();
} catch (KeyManagementException | UnrecoverableKeyException
| NoSuchAlgorithmException | KeyStoreException e) {
System.err.println("创建连接对象异常");
e.printStackTrace();
return;
}
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
try {
HttpPost httppost = new HttpPost("https://www.12306.com");//这里是请求的URL路径
/* 设置参数param到请求对象中去,参数为json对象 如果是json参数,可以使用下面的方法 */
//httpPost.setEntity(new StringEntity(param,"utf-8"));
System.out.println("Executing request " + httppost.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httppost);
try {
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
System.out.println(IOUtils.toString(entity.getContent()));
EntityUtils.consume(entity);
} finally {
response.close();
}
} catch (IOException e) {
System.err.println("客户端返回异常");
e.printStackTrace();
} finally {
try {
httpclient.close();
} catch (IOException e) {
System.err.println("客户端关闭异常");
e.printStackTrace();
return;
}
}
}
总结
1、遇到问题,可以先在网上找下,可能网上会提供各种资料和解决办法,可以尝试着解决,但这些不一定完全适合自己。
2、有时候可以关注国外网站的论坛,特别是官方网站,或许就能找到解决问题的思路。