灵异bug发生
某天广州银行直销银行APP在做兼容性测试的时候,用一个很久不用的的手机(三星 SM-C101)进行测试,发现app无法使用,提示网络异常。但是手机却是满格wifi,也没有设置代理,查看手机系统也是Android 4.2以上的(我们的产品支持的API是14以上,讲道理的话手机系统肯定也是支持的),但是的确每个页面都toast提示“网络连接异常”,实在是诡异。
bug问题排查
先看下网络是不是ok的吧,万能ping百度,百度能访问,说明我们的网络肯定是通的。只好抓包进一步看看是不是后端环境返回的网络异常,但是抓包工具上并没有抓到https请求,进一步地可以排除后端问题。此时非常困惑,为什么百度这些可以使用而我们的APP却无法访问网络呢,然后看了一下我们公司开发的同类的app紫金银行,也是无法使用网络,首先想到的会不会是兼容性问题,想了好久,网上也搜了好久,都没有找到这款旧手机相关的特别信息,历史bug列表也没有改款手机有过类似的bug。最后突然注意到了手机时间,手机时间是2015年1月1日,原来手机时间不知道被谁设置到2015年去了,2015年我们的app还未问世呢,我们APP使用的是https协议,如果手机修改了本地时间,自然是无法访问网络了!
bug引入原因
修改本地时间后,手机为什么https请求失效呢
所以https与本机时间有什么关系呢?
先来了解什么是https协议
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
为了便于理解记忆,有个著名的小故事。
第一话:古代某国边塞驻扎着10万大军。一日,狼烟四起,外敌来犯,为平息叛乱,皇帝派遣A将军去平息边关战事,临行前,皇帝给A将军一行送行,将一枚敕造铜质虎符的一半交与将军,说道:“虎符合并时,边关10万大军,凭此全符调遣,见符如见寡人,切记妥善保管,不可有丝毫缺损。”【对应图中3过程】
第二话: 数日后,A将军抵达边塞大营,驻地B将军再三确认A将军身份,于是乎要求A将军拿出另一半虎符进行拼对,虎符无缝隙对接,确系A将军是皇帝派来的“定边大将”,可与B将军共同统领10万大军。【对应图中1、2、3过程】
第三话:边关大军与来犯敌军两军对阵,排兵布阵通过鼓声号令,重鼓三声,大军前进30步,急鼓5声,方阵变换,骑兵出击,步兵合围。两军展开了激烈的对战……【对应图中5、6过程】
https与本机时间关系
https协议与本机时间相关,https传输的过程中是加密的,而加密的证书里面是携带有效期的,证书的有效期是一个固定的时间段。设备进行https连接的时候,是会携带当前的时间(本机时间)。如果时间不在这个有效期之内,https链接建立的时候,就会报网络无法连接的错误。
问题解决
解决方法1:修改本地手机时间
知道原因后就好解决了,将手机时间设置为随网络时间设定,也就是符合现在的时间。所有的app都可以正常使用了。
但是这个方法有点鸡肋,如果用户手机出现了这类问题,只能通过用户自己去发现问题再设置时间解决问题,用户都是懒的并且基本上是无IT相关知识的,基本上不太可能去排查问题的原因,此时则会面临这类用户的流失。
解决方法2:在代码里面捕获异常,并返回异常提示
如果在使用https证书导入时默认配置验证通过所有链接,这肯定是不安全的,但是某些手机由于内部时间并不是正常时间,在https证书的有效时间外,创建连接时,只想避免这一点时可以捕获时间验证的异常,使其不报错,能正确告诉用户是这是哪一类异常
例如:
(1)此异常是证书已经过期异常,在手机调到证书生效时间之后会捕捉到此异常,提示用户证书已过期
(2)此异常是证书未生效异常,在手机调到证书生效时间之前会捕捉到此异常,提示用户手机当前时间与证书生效时间不符,请调整手机时间
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (chain != null)
for (X509Certificate cert : chain) {
try {
// Make sure that it hasn't expired.
cert.checkValidity();
} catch (CertificateExpiredException e) {
//此异常是证书已经过期异常,在手机调到证书生效时间之后会捕捉到此异常
L.w(TAG, "checkServerTrusted: CertificateExpiredException:" + e.getLocalizedMessage());
} catch (CertificateNotYetValidException e) {
//此异常是证书未生效异常,在手机调到证书生效时间之前会捕捉到此异常
L.w(TAG, "checkServerTrusted: CertificateNotYetValidException:" + e.getLocalizedMessage());
}
try {
// Verify the certificate's public key chain.
cert.verify(cert.getPublicKey());
} catch (CertificateExpiredException e) {
//此异常是证书已经过期异常,在手机调到证书生效时间之后会捕捉到此异常
L.w(TAG, "checkServerTrusted: CertificateExpiredException:" + e.getLocalizedMessage());
} catch (CertificateNotYetValidException e) {
//此异常是证书未生效异常,在手机调到证书生效时间之前会捕捉到此异常
L.w(TAG, "checkServerTrusted: CertificateNotYetValidException:" + e.getLocalizedMessage());
} catch (CertificateException ex) {
//其他异常正常报错
L.w(TAG, "Throw checkClientTrusted: " + ex.getLocalizedMessage());
throw ex;
} catch (NoSuchAlgorithmException e) {
L.w(TAG, "checkServerTrusted: NoSuchAlgorithmException:" + e.getLocalizedMessage());
} catch (InvalidKeyException e) {
L.w(TAG, "checkServerTrusted: InvalidKeyException:" + e.getLocalizedMessage());
} catch (NoSuchProviderException e) {
L.w(TAG, "checkServerTrusted: NoSuchProviderException:" + e.getLocalizedMessage());
} catch (SignatureException e) {
L.w(TAG, "checkServerTrusted: SignatureException:" + e.getLocalizedMessage());
}
}
}
问题解决后带来的启发
这个问题定位的时间过长且曲折,主要是因为测试对https协议工作原理不了解,所以作为测试童鞋,需要对网络协议原理、技术实现原理、网络基础知识都得掌握理解,才能在遇到问题的时候快速定位问题,并驱动开发解决之。
另外,一个困扰自己超过2个小时的问题有必要整理下来,积累多了也是一份宝贵的财富,yeah!