- 异常突现
在这普通的一天,我写普通的代码,却突然收到不普通的报警
javax.net.ssl.SSLHandshakeException: server certificate change is restrictedduring renegotiation
查看日志访问的请求全部报错,紧急联系对方,得知对方更换了服务器证书。由于连接池会缓存连接,旧连接不能及时释放,线上一直在持续报警,最终刷新所有缓存,业务才全部恢复正常。
- 提出疑问
虽然系统恢复了正常,但是有几个问题一直留在我心里:
为什么会出现这个异常?
HttpClient 是如何进行https请求的?
除了重启机器,是否还有其他应对方式?
3. 初探Https
本文主要从源码的角度,探寻HttpClient如何进行Https请求,以及Java是如何进行SSL连接,不涉及SSL/TLS具体协议内容。
3.1 Https基础知识
Https的基础知识前辈们已经有了很好的总结,推荐几篇博文,可以学习一下。
HTTPS深入理解
HTTPS协议详解 系列
为了更好的理解下文,这里引用两个SSL/TLS握手流程,摘自 HTTPS深入理解
验证服务器握手过程 图1
图片来自网络
(1) 客户端通过Client Hello消息将它支持的SSL版本、加密算法、密钥交换算法、MAC算法等信息发送给SSL服务器。
(2) 服务器确定本次通信采用的版本和加密套件,并通过Server Hello消息通知给客户端。如果服务器允许客户端在以后的通信中重用本次会话,则服务器会为本次会话分配会话ID,并通过Server Hello消息发送给SSL客户端。
(3) 服务器将携带自己公钥信息的数字证书通过Certificate消息发送给客户端。
(4) 服务器发送Server Hello Done消息,通知客户端版本和加密套件协商结束,开始进行密钥交换。
(5) 客户端验证服务器的证书合法后,利用证书中的公钥加密客户端随机生成的premaster secret,并通过Client Key Exchange消息发送给SSL服务器。
(6) 客户端发送Change Cipher Spec消息,通知服务器后续报文将采用协商好的密钥和加密套件进行加密和MAC计算。
(7) 客户端计算已交互的握手消息(除Change Cipher Spec消息外所有已交互的消息)的Hash值,利用协商好的密钥和加密套件处理Hash值(计算并添加MAC值、加密等),并通过Finished消息发送给服务器。服务器利用同样的方法计算已交互的握手消息的Hash值,并与Finished消息的解密结果比较,如果二者相同,且MAC值验证成功,则证明密钥和加密套件协商成功。
(8) 同样地,SSL服务器发送Change Cipher Spec消息,通知客户端后续报文将采用协商好的密钥和加密套件进行加密和MAC计算。
(9) 服务器计算已交互的握手消息的Hash值,利用协商好的密钥和加密套件处理Hash值(计算并添加MAC值、加密等),并通过Finished消息发送给客户端。客户端利用同样的方法计算已交互的握手消息的Hash值,并与Finished消息的解密结果比较,如果二者相同,且MAC值验证成功,则证明密钥和加密套件协商成功。
(10) 客户端接收到服务器发送的Finished消息后,如果解密成功,则可以判断服务器是数字证书的拥有者,即服务器身份验证成功,因为只有拥有私钥的服务器才能从Client Key Exchange消息中解密得到premaster secret,从而间接地实现了客户端对服务器的身份验证。
重用会话的握手过程 图2
协商会话参数、建立会话的过程中,需要使用非对称密钥算法来加密密钥、验证通信对端的身份,计算量较大,占用了大量的系统资源。为了简化SSL握手过程,SSL允许重用已经协商过的会话,具体过程为:
(1) 客户端发送Client Hello消息,消息中的会话ID设置为重用的会话的ID。
(2) 服务器如果允许重用该会话,则通过在Server Hello消息中设置相同的会话ID来应答。这样,客户端和服务器就可以利用原有会话的密钥和加密套件,不必重新协商。
(3) 服务器发送Change Cipher Spec消息,通知客户端后续报文将采用原有会话的密钥和加密套件进行加密和MAC计算。
(4) 服务器计算已交互的握手消息的Hash值,利用原有会话的密钥和加密套件处理Hash值,并通过Finished消息发送给客户端,以便SSL客户端判断密钥和加密套件是否正确。
(5) 客户端发送Change Cipher Spec消息,通知SSL服务器后续报文将采用原有会话的密钥和加密套件进行加密和MAC计算。
(6) 客户端计算已交互的握手消息的Hash值,利用原有会话的密钥和加密套件处理Hash值,并通过Finished消息发送给SSL服务器,以便SSL服务器判断密钥和加密套件是否正确。
3.2 HttpClient 如何处理https/http请求
先看一段小代码,这是一个简单的请求两次考拉主页的代码。通过之后的代码可以看到,在进行第二次请求时,并不会重新建立连接。
public static void main(String[] args) throws IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet h