问题症状
最近把一部分服务迁移到用 rancher 管理的容器集群环境里面去了,感觉还是不错的,而且 rancher 里自带的负载均衡是 haproxy,配置上域名证书后由 haproxy 来负责 https 的处理,后端就不用配 https 了。服务跑起来之后,在日志中发现出现类似下面错误:
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
出现地方是在用 HttpClient 请求我们自己的 https 域名时报错,看起来是域名的证书没法验证通过。
问题初探
网上搜索了之后发现有很多相关的内容,但是很多都是因为访问自签名证书的域名而产生的问题,给出的方案都是在代码里取消了对域名证书的验证。而我们的域名签名是有效的,不应该会出现这种问题,我们也不需要为了解决自签名证书去取消验证,所以药不对症,得继续寻找答案。
本地调试
用 HttpClient 在本地写了简单的测试代码运行,发现同样报错,缩小了问题范围。关键点:设置 System.setProperty("javax.net.debug", "ssl");
打开 ssl 的调试信息,可以获得相当多关键信息。在我的代码里,出错信息如下:
handling exception: javax.net.ssl.SSLHandshakeException:Remote host closed connection during handshake
网上搜索了之后逐渐发现了关键问题所在: 域名证书是正常的,但 SSL 握手失败,判断是 JDK 使用的 SSL 协议和加密算法与我们的 WebServer 不匹配。
SSL Server 兼容性检查
这里要祭出一个强力法器了 https://www.ssllabs.com/ssltest/ 这个工具可以对域名的证书做详细的检查,更实用的功能是可以反馈各种 User Agent 访问的可用性,把我们的域名丢进去检测一下,得分 A 还不错,再仔细看看下面的列表…jdk6 jdk7 都显示无法连接…简直是个大悲剧。
谜底揭晓
这里涉及到 HTTPS 的几个概念,Protocols 和 Cipher Suites。一般来说,在给网站或应用做 SSL 加密时,Protocol 有以下几种:TLS1.2、TLS1.1、TLS1.0、SSLv3、SSLv2。不同的协议下使用的 Cipher Suites 也有不同。SSLv3 和 SSLv2由于前两年被爆出的严重安全漏洞,目前已经被认为不适合使用了,稍微新一点的 webserver 默认都是关闭这两项的,关键在于 TLS1.0。通过上面那个检查 SSL 的网站发现我们的服务器(即 rancher 中的 haproxy),是只支持了 TLS1.2 和 TLS1.1 这两项的,实际上这也是现代服务器的推荐配置。
重点来了! jdk6 默认是只支持 TLS1.0 的,jdk7 支持 TLS1.1 和 TLS1.2 但是默认是不开启的,只有 jdk8 才默认选择最高的 TLS1.2。所以,只要是运行在 jdk6 和 jdk7 上,就有可能遇到这种问题。并且,我尝试了在 jdk7 中开启 TLS1.1 和 TLS1.2 的支持,但还是同样的错误,发现可能是由于我们的证书支持的 Cipher Suites 和 jdk7 中支持的对不上,还是验证失败。
解决方案
到这里怎么解决问题就比较清楚了,可以将部署了应用的 jetty 容器升级到 jdk8 的环境,继续使用 TLS1.2、TLS1.1 的配置组合即可。但是这样实际上还是放弃了对一部分旧 user agent 的支持,从上面那个检查的列表中来看,Android4.4 以下都是默认只支持 TLS1.0 的,所以出于更好的访问兼容性,还是打开对 TLS1.0 的支持比较好。
配置 Nginx支持 TLS1.0
修改nginx配置
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
ssl_prefer_server_ciphers on;
原创类文章未经允许请勿转载:39点博客 » 解决 javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated 问题