1. 从一次真实的“握手失败”说起
那天下午,我正在调试一个老旧的Java 7系统,它需要调用一个外部合作伙伴的API。代码很简单,就是最经典的 HttpsURLConnection 去发起一个GET请求。然而,控制台毫不留情地抛出了一堆红字:
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
...
Caused by: SSL peer shut down incorrectly
相信很多还在维护Java 7甚至更老系统的朋友,对这个异常信息再熟悉不过了。它就像一个神秘的“暗号”,告诉你连接建立失败了,但具体为什么失败,却说得云里雾里。我当时的第一反应是:“证书有问题?” 检查了信任库,没问题。“网络不通?” Ping了一下,通着呢。这就很让人头疼了。
后来,我打开了SSL握手调试日志(加上 -Djavax.net.debug=ssl:handshake 这个JVM参数),真相才浮出水面。在日志里,我清晰地看到我的客户端(Java 7)热情地伸出手说:“嗨,咱们用TLS 1.0来安全地聊天吧!” 而对面的服务器却高冷地回复了一句:“我只跟TLS 1.1或1.2的朋友玩。” 然后,啪的一声,连接就被服务器直接关闭了。这就是“握手失败”的本质:协议版本没谈拢。
这个场景在今天越来越普遍。随着网络安全标准的提升,很多现代的Web服务器、云服务API(比如一些新的支付网关、云存储服务)都已经默认禁用甚至不再支持老旧的TLS 1.0协议了,转而强制要求使用更安全的TLS 1.2或更高版本。而我们的Java 7应用,如果还保持着出厂设置,就会在这场“初次见面”的问候语上栽跟头。所以,这个问题不是你的代码逻辑错了,而是你的“安全方言”版本太旧,对方听不懂,也不想听。
2. 深入TLS协议:为什么Java 7会“语言不通”
要彻底搞懂这个问题,我们得稍微深入一点,看看TLS(传输层安全协议)和Java 7到底是怎么回事。你可以把TLS想象成一套不断升级的“加密通话规则”。
TLS 1.0 诞生于1999年,基于更早的SSL 3.0,算是现代安全通信的奠基者。TLS 1.1(2006年)和 TLS 1.2(2008年)则是重要的安全升级版,修复了前代的一些漏洞,增加了更强的加密算法。到了 TLS 1.3(2018年),变化就更大了,速度更快、更安全。
那么,Java 7在这个时间线上处于什么位置呢?Java 7的初始版本发布于2011年。在那个年代,TLS 1.2虽然已经诞生,但还未像今天这样成为绝对主流。因此,Sun(现在是Oracle)的JSSE(Java安全套接字扩展)实现,在Java 7的默认配置里,启用的协议列表是 SSLv3, TLSv1。这意味着,当你创建一个 SSLSocket 或使用 HttpsURLConnection 时,如果没有特别指定,它会优先尝试用TLS 1.0去和服务器握手。
这里有个关键点需要澄清:Java 7的JSSE实现,是具备支持TLS 1.1和TLS 1.2的能力的,尤其是在Java 7的后期更新版本中(比如Java 7 Update 95之后)。问题不在于“能不能”,而在于“默认用不用”。这就像你的手机支持5G网络,但默认设置可能为了省电而优先连接4G。服务器那边呢,现在普遍的做法是只开启TLS 1.2(或1.1+1.2),明确关闭了TLS 1.0。两边的默认列表对不上,握手自然失败。
所以,SSLHandshakeException: Remote host closed conn

3941

被折叠的 条评论
为什么被折叠?



