一次IOS通知推送問題排查全過程
原創:打碼日記(微信公眾號ID:codelogs),歡迎分享,轉載請保留出處。
發現問題
在上周一個將要下班的夜晚,測試突然和我打招呼,說IOS推送的修復更新上線後存在問題,後台報錯。
連忙跑到測試那裡看報錯詳情,報錯如下:
重現問題
看到這個報錯後,在網上搜索了一下,這種錯誤一般都是因為客戶端不信任服務端SSL證書導致的,回想工作以來,好像遇到這種問題好多次了,只要將證書導入一下就好了。
由於不能冒然在線上修改解決問題,於是獲取推送相關信息(如:設備token)後,到自己電腦上去測試,看是否能重現問題。
啪啦啪啦,代碼修改完畢,點運行坐等錯誤出現。
5秒鐘過後,發現推送消息發送成功了,沒有出現報錯,有點懵逼!心裏想,代碼都是完全一樣的啊,怎麼線上報錯,我這卻是好的呢???
糾結了一會,於是開始靜下心來分析:
- 代碼肯定是一樣的,應該不是表面上的代碼原因。
- 其次推送設備也是一樣的,應該也不是手機問題。
- 那麼。。。
就在沒有頭緒之際,我又掃了一眼工程目錄,發現了jdk8,但我們線上系統使用的是jdk7啊。
於是我將自己工程的jdk8換成jdk7試一下,同樣的報錯終於出現了!
那為什麼jdk8沒問題,jdk7卻報錯呢???
尋找原因
圍繞着網上的說法和之前的經驗,這hand_failure
錯誤應該是SSL/TLS握手過程中不信任服務端證書導致的,那辦法很簡單,去蘋果官網找到蘋果服務端提供的證書,導入到java的證書信任庫cacert文件中即可。
於是,下載了證書文件GeoTrust_Global__CA.cer
,並用jdk自帶的keytool工具將證書導入到cacert文件中,如下:
如上,導入過程中,發現提示已經有這個證書了,當時沒想那麼多,再導一次試試吧!
導入後,再次運行代碼,結果還是報錯!
心裏又慌亂起來,沒招了,於是又百度/google去了,但搜索出來的結論幾乎都是證書信任問題,和自己的招式一樣!
同時,也了解了一下SSL/TLS協議相關知識,大致握手過程如下:
詳細如下:
-
客戶端發送clientHello消息,告訴服務端我使用的TLS版本與加密套件等。
-
服務器返回serverHello消息,告訴自己選擇哪個TLS協議版本與加密套件等。
-
服務器發送Certification消息,將自己的數字證書(包括服務器名稱、CA和公鑰)作為消息內容發給客戶端。
-
客戶端Certificate verify校驗服務器的數字證書的有效性。
-
客戶端Change cipher spec選擇加密套件並生成會話密鑰(客戶端與服務器之間後續的數據傳輸將使用此會話密鑰)。
-
客戶端、服務器之間發送加密數據。
另外,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網絡異常場景分析
字符編碼解惑