• 一次IOS通知推送问题排查全过程


    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。

    发现问题#

    在上周一个将要下班的夜晚,测试突然和我打招呼,说IOS推送的修复更新上线后存在问题,后台报错。

    连忙跑到测试那里看报错详情,报错如下:
    image_2022-05-04_20220504143349

    重现问题#

    看到这个报错后,在网上搜索了一下,这种错误一般都是因为客户端不信任服务端SSL证书导致的,回想工作以来,好像遇到这种问题好多次了,只要将证书导入一下就好了。

    由于不能冒然在线上修改解决问题,于是获取推送相关信息(如:设备token)后,到自己电脑上去测试,看是否能重现问题。

    啪啦啪啦,代码修改完毕,点运行坐等错误出现。

    5秒钟过后,发现推送消息发送成功了,没有出现报错,有点懵逼!心里想,代码都是完全一样的啊,怎么线上报错,我这却是好的呢???

    纠结了一会,于是开始静下心来分析:

    1. 代码肯定是一样的,应该不是表面上的代码原因。
    2. 其次推送设备也是一样的,应该也不是手机问题。
    3. 那么。。。

    就在没有头绪之际,我又扫了一眼工程目录,发现了jdk8,但我们线上系统使用的是jdk7啊。

    image_2022-05-04_20220504143511

    于是我将自己工程的jdk8换成jdk7试一下,同样的报错终于出现了!

    那为什么jdk8没问题,jdk7却报错呢???

    寻找原因#

    围绕着网上的说法和之前的经验,这hand_failure错误应该是SSL/TLS握手过程中不信任服务端证书导致的,那办法很简单,去苹果官网找到苹果服务端提供的证书,导入到java的证书信任库cacert文件中即可。

    image_2022-05-04_20220504143534

    于是,下载了证书文件GeoTrust_Global__CA.cer,并用jdk自带的keytool工具将证书导入到cacert文件中,如下:
    image_2022-05-04_20220504143548
    如上,导入过程中,发现提示已经有这个证书了,当时没想那么多,再导一次试试吧!

    导入后,再次运行代码,结果还是报错!

    心里又慌乱起来,没招了,于是又百度/google去了,但搜索出来的结论几乎都是证书信任问题,和自己的招式一样!

    image_2022-05-04_20220504143607

    同时,也了解了一下SSL/TLS协议相关知识,大致握手过程如下:

    image_2022-05-04_20220504143625

    详细如下:

    1. 客户端发送clientHello消息,告诉服务端我使用的TLS版本与加密套件等。

    2. 服务器返回serverHello消息,告诉自己选择哪个TLS协议版本与加密套件等。

    3. 服务器发送Certification消息,将自己的数字证书(包括服务器名称、CA和公钥)作为消息内容发给客户端。

    4. 客户端Certificate verify校验服务器的数字证书的有效性。

    5. 客户端Change cipher spec选择加密套件并生成会话密钥(客户端与服务器之间后续的数据传输将使用此会话密钥)。

    6. 客户端、服务器之间发送加密数据。

    另外,jvm有一个-Djavax.net.debug=SSL的参数,可以把SSL/TLS的握手过程都在控制台通过日志显示出来,如下:

    image_2022-05-04_20220504143642

    Ok,既然知道了握手过程,那就把SSL/TLS的日志显示出来看一下吧,看看是什么阶段出现的问题,如下:

    image_2022-05-04_20220504143658
    这说明GeoTrust_Global.cer证书确实已经添加到信任库中了,而详细的SSL日志如下:

    image_2022-05-04_20220504143712

    如上图,报错发生在clientHello发送之后,在读服务端返回的serverHello消息时,却读到的是Alert:handshake_failure警告信息,然后SSL握手就中断了,就好像你在和服务器打招呼,然后服务器回复了一个滚蛋一样!

    百思不得其解?

    准备放弃#

    一会,我看到测试走了过来。

    测试:“问题解决的怎么样了,啥情况啊”

    :“还不知道,本来以为很简单,实际上并没有,jdk8可以,7不行”

    测试:“好吧”

    :”要不升级jdk8吧“

    测试:”能行吗“

    :”......能行,jdk兼容性都很好,而且其它系统都已经升级过了,这个系统本来也计划要升,问题不大“

    测试:”......好的“

    于是,测试去升级jdk8了,本来我应该站在测试身后等待问题被解决,但我还是想在这期间查一查根本原因,于是又琢磨了起来。

    发现真相#

    回想起,这个系统以前本来是jdk6,就是因为苹果强制开发者必须使用Https,且需要TLSv1.2版本,而jdk7才开始支持TLSv1.2,所以这个系统才升级到jdk7,那现在这个报错会不会...

    于是,我赶紧使用TLSv1.2试试,添加jvm参数-Dhttps.protocols=TLSv1.2即可强制https使用TLSv1.2版本协议,如下:

    image_2022-05-04_20220504143729

    加完后再次运行,报错依旧,那还有那里可能会有问题呢?

    我突然灵光一闪,使用jdk7运行一次,再使用jdk8运行一次,将两次的SSL日志对比一下,看看哪里不一样,说不定能找到线索!

    于是我马上试运行并对比两次SSL日志,发现两次SSL日志中TLS协议使用的加密套件不同,如下图:

    jdk7的调试信息如下 :
    image_2022-05-04_20220504143747
    jdk8的调试信息如下:
    image_2022-05-04_20220504143802
    jdk8的TLS握手在服务端ServerHello之后,选择的加密套件是TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,而jdk7中可供选择的加密套件中没有TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

    问题差不多清晰了,应该是jdk7中的加密套件,苹果服务器觉得太老不安全,都不支持了,那么如果我使用jdk8,并将加密套件强制为jdk7中的加密套件呢,应该也是会报错的!

    于是,我还是使用jdk8,并添加了jvm参数-Dhttps.cipherSuites=SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5,SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA,以强制jdk8使用jdk7的加密套件,如下:

    image_2022-05-04_20220504143817

    再次运行后,果然报错了!所以应该是jdk8支持了一些新的加密套件,而苹果服务端只认这些新的加密套件导致的。

    于是,我去jdk官网,查了一下jdk8的新增特性,发现TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384中AES加密算法的GCM模式在jdk8中才支持,如下:

    image_2022-05-04_20220504143832

    至此,问题原因全部搞清楚了,测试升级jdk8后也报告推送正常,此时已是10点多,两人写写日报赶紧回家去了......

    往期内容#

    密码学入门
    时区的坑,不想再踩了!
    常见的Socket网络异常场景分析
    字符编码解惑

  • 相关阅读:
    vue+Vant,关闭Popup弹框,遮罩层并没有消失
    【实习】DLL相关
    CSS基础入门03
    【金三银四】面试题汇总(持续编写中)
    前端工作总结114-JS-JS创建数组的三种方法
    用DIV+CSS技术设计的水果介绍网站(web前端网页制作课作业)
    7.网络原理之TCP_IP(下)
    数字签名算法类别及用途
    计算机网络4——网络层2
    爱奇艺的架构到底有多牛?
  • 原文地址:https://www.cnblogs.com/codelogs/p/16221107.html