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

发现问题

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

连忙跑到测试那里看报错详情,报错如下:

重现问题

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

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

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

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

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

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

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

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

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

寻找原因

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

于是,下载了证书文件GeoTrust_Global__CA.cer,并用jdk自带的keytool工具将证书导入到cacert文件中,如下:



如上,导入过程中,发现提示已经有这个证书了,当时没想那么多,再导一次试试吧!

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

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

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

详细如下:

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

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

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

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

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

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

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

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



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

如上图,报错发生在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版本协议,如下:

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

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

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

jdk7的调试信息如下 :



jdk8的调试信息如下:



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的加密套件,如下:

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

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

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

往期内容

密码学入门

时区的坑,不想再踩了!

常见的Socket网络异常场景分析

字符编码解惑

一次IOS通知推送问题排查全过程的更多相关文章

  1. iOS 通知推送APNS

    结合网上各个资料,再简单整理的一份. 一.APNS推送说明 1.你的IOS应用需要去注册APNS消息推送功能. 2.当苹果APNS推送服收到来自你应用的注册消息就会返回一串device token给你 ...

  2. 【转】iOS消息推送实现过程记录

    客户端代码:链接地址 服务器代码:链接地址  链接地址 这里记录下iOS消息推送实现的全过程 首先,申请秘钥. 之后进入链接地址开发者,当然你得有啊!!!!! 点击这里 如图: 下面实现创建推送证书( ...

  3. IOS之推送通知(本地推送和远程推送)

    推送通知和NSNotification是有区别的: NSNotification:是看不到的 推送通知:是可以看到的 IOS中提供了两种推送通知 本地推送通知:(Local Notification) ...

  4. iOS 10推送通知开发

    原文地址:Developing Push Notifications for iOS 10,译者:李剑飞 虽然通知经常被过度使用,但是通知确实是一种获得用户关注和通知他们需要更新或行动的有效方式.iO ...

  5. (七十三)iOS本地推送通知的实现

    iOS的推送通知分为本地推送和网络推送两种,如果App处于挂起状态,是可以发送本地通知的,如果已经被杀掉,则只有定时通知可以被执行,而类似于QQ的那种网络消息推送就无法实现了,因为App的网络模块在被 ...

  6. xamarin.ios 本地通知推送

    由于ios10版本以后UILocalNotification被标为弃用了,所以要添加新的本地通知推送功能,下面提供一些代码参考. 一.先在AppDelegate.cs上注册本地通知推送功能. publ ...

  7. IOS远程推送

    IOS远程推送 一.关于推送通知 推送通知,也被叫做远程通知,是在iOS 3.0以后被引入的功能.是当程序没有启动或不在前台运行时,告诉用户有新消息的一种途径,是从外部服务器发送到应用程序上的.一般说 ...

  8. iOS 消息推送(APNs) 傻瓜式教程

    也可以去我的简书页面查看这篇文章 首先: 1.做iOS消息推送需要真机测试 2.做iOS消息推送需要有付费的开发者账号 是否继续看帖? 先学习一下相关的知识吧! 因为中途可能会遇到一些问题,这篇文章或 ...

  9. (转载)iOS 极光推送SDK 集成指南

    iOS SDK 集成指南 使用提示 本文匹配的 SDK版本:r1.2.5 以后. 查看最近更新了解最新的SDK更新情况. 产品功能说明 极光推送(JPush)是一个端到端的推送服务,使得服务器端消息能 ...

随机推荐

  1. springcloud断路器作用?

    当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)断路器有完全打开状态:一段时间内 达到一定的 ...

  2. 构造器(constructor)是否可被重写(override)?

    构造器不能被继承,因此不能被重写,但可以被重载.

  3. Java 中 ConcurrentHashMap 的并发度是什么?

    ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安 全.这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一 个可选参数,默认 ...

  4. Redis 集群,集群的原理是什么?

    1).Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为master,继续提供服务. 2).Redis Cluster 着眼于扩展性,在单个 redis ...

  5. 学习Tomcat(一)

    一.jdk和tomcat基础 1.web应用对比 apache: 两种方式运行php,一是用模块,二是用fastcgi nginx: 通过fastcgi处理动态请求,也可转发到tomcat 2.tom ...

  6. 剑指Offer9——使用双栈模拟队列

    剑指Offer9--使用双栈模拟队列 队列Queue是具有FIFO(First in First out)特性的数据结构,栈Stack是具有LIFO(后进先出)特性的数据结构.下面提供一种思路使用双栈 ...

  7. leetcode_两数相加

    给你两个 非空 的链表,表示两个非负的整数.它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字. 请你将两个数相加,并以相同形式返回一个表示和的链表. 你可以假设除了数字 0 ...

  8. H5移动端开发

    开发前准备 环境: Node.js LTS版本 git 最新版 文档: 本项目技术栈基于 ES2016 VueJS vux 快速开始 1.克隆项目 git clone https://gitee.co ...

  9. java中方法重载是什么, 实际中到底有什么意义, 用处?请举例

    7.3 方法重载(overload)  当几个方法有相同的方法名,但参数个数不同或参数类型不同时,就涉及方法重载 方法重载有什么意义呢?在公司里编程,有时候一个方法名,要用到很多次,而且每次跟每次的参 ...

  10. vue项目处理dpr和多屏幕适配问题

    <!DOCTYPE html> <html style="font-size:37.5px"> <head> <meta charset= ...