本文重在排查问题的过程,解决方法得视情况而定。网上千篇一律的解决方法,可能都不适合您,所以排查问题过程才是重中之重。
问题:
系统升级了一些jar(漏洞扫描,必须升级)。请求某银行失败,异常堆栈信息:javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure,回滚系统一切正常。
原因分析:
针对问题,有2个方向(正方向:从升级jar方向排查,反方向:从错误原因出发)
1、正方向:
由于是通讯层面的错误,怀疑是升级httpClinet(4.5.1升级到4.5.13)导致。于是百度发现httpClient升级列表内容:https://downloads.apache.org/httpcomponents/httpclient/RELEASE_NOTES-4.5.x.txt, 查到这里笔者看了看升级内容,一脸懵逼,不能看出是什么问题导致本次错误。笔者这里采用死办法(都是因为自己太菜),用二分查找法,直接把jar升级到4.5.8,测试该本不行,于是回退4.5.6结果又可以,就这样最终锁定4.5.8版本的问题,查看4.5.8版本升级内容如下图所示,至此正方向无疾而终(但对后续解决方法有用,知道问题之根源)。
2、反方向:
handshake_failure错误,一看就是ssl握手错误(不知道,一百就一目了然),对应ssl握手过程,笔者不在叙述(笔者也是菜鸟),只献上一张图:
由于笔者对ssl握手没有深入理解,导致排查问题用了很长一段时间,没有快速定位,所以先把SSL握手目的提前警示给大家,希望对后续读者有帮助(提示:目的1非常重要)。
SSL握手主要有三个目的:
1. 客户端与服务器需要就一组用于保护数据的算法达成一致;
2. 它们需要确立一组由那些算法所使用的加密密钥;
3. 握手还可以选择对客户端进行认证。
笔者百度了半天,才弄来一个参数(打开ssl debug日志):-Djavax.net.debug=ssl ,运行时加上即可(不知道怎样加也可以百度,因为笔者有过百度idea的经历)。
测试正常请求日志:
测试异常请求日志:
从异常日志看出,client_hello就异常了,在这里笔者,还以为找到了答案,因为网上千篇一律的说TLSv1.xx协议不一样,从异常日志所得确实不一样,于是按照百度所得改协议,发现没啥用,而且日志打印还是一模一样的错误(不排除读者没改对,备注:这个参数笔者都改过:jdk.tls.disabledAlgorithms)。
后来笔者又仔细看了正常请求日志,发现正常请求SSL协议也不一致,我方是TSLv1.2,对方是SSLv3,也是正常的,笔者就无从着手了,于是每一行,每一行的日志对比(心酸得很,都是因为对SSL握手理解不透彻),发现是Cipher Suites 包含的算法不一样,支持的算法少了很多!到此时笔者突然想到正方向升级httpClient内容过滤掉了一些弱密码组件,肯定是这里的问题,又仔细看看了正常日志,发现调用方始终用的是SSL_RSA_WITH_3DES_EDE_CBC_SHA加密方式,而升级httpClient后,打印的我方支持的密码组件没有这个(Cipher Suites列表没有)。猜测就是这个原因了(前文提到SSL目的1,客户端与服务器需要就一组用于保护数据的算法达成一致),这里明显不一致,所以握手失败了,于是查看httpClient源码。
验证上面结论是否可以行,测试传递密码套件SSL_RSA_WITH_3DES_EDE_CBC_SHA(本例中,只改了httpClientUtils工具类),果然测试通过(握手成功,交易正常)。
回顾及结论:
1、对SSL握手过程理解不是很清楚,本文client_hello阶段握手失败,客户端把支持的加密套件、版本号信息等发给服务端,以供服务端选取合适支持的加密套件。然儿本例中,服务端指定了SSL_RSA_WITH_3DES_EDE_CBC_SHA加密套件,升级httpClient过滤掉了该弱密码套件,所以导致ssl握手失败。
2、网上千篇一律的说是两边(客户端与服务端)支持的"SSLv3","TLSv1","TLSv1.1","TLSv1.2",不一样,其实也是正确的。在本例中服务端采用的是jdk1.6支持的SSLv3协议并指定了SSL_RSA_WITH_3DES_EDE_CBC_SHA加密套件,而我方(客户端)client_hello发送支持加密套件不支持此套件,所以导致握手失败。如果服务端没有指定加密算法,那么我方(客户端)指定协议是可以握手成功的。
3、遇到问题,我们不能盲目百度,生搬硬套,要善于思考。看似复杂,无从下手的问题,往往解决方案非常简单。还是笔者中学老师说的对,真相往往真的在表面,正确答案(真相)在看你,而你确视而不见。