iOS开发中使用的通信协议,除了聊天和物联网相关的APP可能会用到XMPP和MQTT,剩下的应用通常都只会用到HTTP和HTTPS这两种协议,而由于ATS的限制,iOS默认都是实用HTTPS,除非修改项目的info.plist:
对于HTTPS,熟悉HTTPS协议的人都知道,HTTPS基本上就是在HTTP协议的基础上加了一层SSL/TLS加密协议,这层加密协议在建立通信时除了HTTP本身的3次握手外,还会再经历额外的身份校验、数据加解密过程:
这些过程中会混合使用非对称加密、对称加密、证书校验和数字签名等手段,其中证书校验的过程:
- 首先,服务器的运营人员向数字证书认证机构提出公开密钥的申请。数字证书认证机构在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公钥证书后绑定在一起。
- 服务器会将这份由数字证书认证机构颁发的公钥证书发送给客户端,以进行公开密钥加密方式通信。公钥证书也可叫做数字证书或直接称为证书。
- 接到证书的客户端可使用数字证书认证机构的公开密钥,对那张证书上的数字签名进行验证,一旦验证通过,客户端便可明确两件事:一,认证服务器的公开密钥的是真实有效的数字证书认证机构。二,服务器的公开密钥是值得信赖的。
- 此处认证机关的公开密钥必须安全地转交给客户端。使用通信方式时,如何安全转交是一件很困难的事。因此,多数浏览器开发商发布版本时,会事先在内部植入常用认证机关的公开密钥。
而通常在开发环境中服务端会使用自签名证书,或者即使在生产环境中可能也会使用免费的证书,这些证书的认证机构可信度通常低于那些收费的大型认证机构,这样,在步骤3的时候客户端对证书的认证可能就失败了。最近在开发公司的iOS SDK就碰到这个问题,直接调用HTTPS接口会报错:
HTTP load failed (kCFStreamErrorDomainSSL, -9843)
明白了原理后,解决方案就简单了,只需要客户端在校验时对使用的域名设置白名单就行了,以下是使用NSURLSession时需要在delegate上做的修改:
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"recvapi.log.dtstack.com"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
}
这样就可以解决开发时的证书校验失败的问题。
但这只限于开发阶段,治标不治本,不能在生产环境下使用,要想治本,最好还是更换服务器证书,找个可信度高的大型认证机构重新弄份证书。
服务端更换证书也简单,以Nginx为例,拿到证书后,修改Nginx的配置:
server {
listen 443 ssl;
server_name example.com;
ssl on;
ssl_certificate /etc/ssl/private/example_com.crt;
ssl_certificate_key /etc/ssl/private/example_com.key;
}
然后重启服务就行了:
nginx -t && nginx -s reload