iOS开发之HTTPS
在今年的WWDC 2016苹果开发者大会上,苹果再一次提到了数据安全方面,并且宣布iOS应用将从2017年1月起启用名为ATS的安全传输功能,将强制使用HTTPS安全连接。
首先,ATS 安全标准是苹果在发布 iOS 9 和 OS X EI Capitan 系统时发布的,这一标准通过强行推动一系列安全实际操作,从而积极促进安全性,同时还要求网络请求必须在一个安全的链接上传输,当开启 ATS 之后,网络传输将自动通过 HTTPS 协议传输而不是 HTTP 协议。启用HTTPS网络连接之后,数据传输的安全性将大幅提升,不容易被黑客拦截破译。
那么HTTPS相对于HTTP到底有何区别和优势呢?互联网全站HTTPS的时代已经到来,这篇文章其实已经给我们做了比较清楚的对比和分析,如果关于两者的基础知识还不太理解的,我建议可以阅读一些相关资料,比如马海祥博客
HTTP与HTTPS的区别也对两者做了基本分析,值得一读。
这里我想给大家分享的不是基础知识点,也不是深入剖析两者的优劣,因为已经有很多资历深厚的前辈给我们做了大量的教程,只要你不懒,我相信是可以找到更多学习资料的。
所以今天我们还是从iOS开发入手,就聊一下关于HTTP和HTTPS到底该怎么使用,两者的实际开发流程有什么区别。
做iOS开发的对下面这段代码应该比较熟悉了:
Snip20160918_1.png自从苹果开始大力推崇HTTPS之后,我们使用HTTP连接就需要在info.plist进行上面的配置,不然就会出现下面这种报错:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
这是因为xcode7之后,苹果要求APP使用HTTPS协议,修改方法是要么使服务器支持https访问,要么设置允许不安全的网络协议。我们常用的还是直接添加设置允许,所以就用到了上面的方法。那么问题来了,从2017年开始,苹果将强制全面采用HTTPS连接,目前国内能全站支持HTTPS的网络公司基本没有,或者少之又少,但是对于这样的发展趋势,我们不得不重视。对于开发者来说,开发安全的APP更是体现我们对用户负责的态度。
官方文档对HTTPS验证也有详细的说明。这里我们就说一下使用NSURLConnection和NSURLSession建立HTTPS连接以及AFNetworking对HTTPS的支持及使用方法。
一、NSURLConnection
首先,创建连接对象
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event
{
//1,URL
NSURL* url = [NSURL URLWithString:@"https://m.baidu.com"];
//2,请求
NSURLRequest* request = [NSURLRequest requestWithURL:url];
//3,链接
NSURLConnection* connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
//开始
[connection start];
}
然后设置代理方法,进行进行连接请求
//#pragma mark- NSURLConnectionDataDelegate
//收到安全警告时候调用
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
//验证方式一:
//1.判断是否信任服务器
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//2.获取到受保护空间里面的证书
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//3.告诉服务器我信任你,我们建立安全通道
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else {
//5)验证失败,取消这次验证流程
[challenge.sender cancelAuthenticationChallenge:challenge];
}
//验证方式二:
//1)获取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//2)SecTrustEvaluate对trust进行验证
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess &&
(result == kSecTrustResultProceed ||
result == kSecTrustResultUnspecified)) {
//3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
} else {
//5)验证失败,取消这次验证流程
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
最后处理连接数据,监控连接过程:
//初始化
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.dataM = [NSMutableData data];
}
//接收数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.dataM appendData:data];
}
//错误信息
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"error---%@",[error localizedDescription]);
}
//输出结果
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%@",[[NSString alloc]initWithData:self.dataM encoding:NSUTF8StringEncoding]);
}
上面是代码是通过系统默认验证流程来验证证书的。假如我们是自建证书的呢?这样Trust Object里面服务器的证书因为不是可信任的CA签发的,所以直接使用SecTrustEvaluate进行验证是不会成功。又或者,即使服务器返回的证书是信任CA签发的,又如何确定这证书就是我们想要的特定证书?这就需要先在本地导入证书,设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),再调用SecTrustEvaluate来验证。代码如下
//先导入证书
NSString * cerPath = ...; //证书的路径
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];
//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//1)获取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//注意:这里将之前导入的证书设置成下面验证的Trust Object的anchor certificate
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);
//2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess &&
(result == kSecTrustResultProceed ||
result == kSecTrustResultUnspecified)) {
//3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
} else {
//5)验证失败,取消这次验证流程
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
二、NSURLSession
首先创建session对象并进行懒加载
-(NSURLSession *)session
{
if (!_session)
{
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
然后创建连接对象
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event
{
//1,URL
NSURL* url = [NSURL URLWithString:@"https://m.baidu.com"];
//2,链接
[[self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data.length > 0)
{
NSString* result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
}
else
{
NSLog(@"error---%@",[error localizedDescription]);
}
}] resume];
}
最后监控连接,进行判断
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
//1.判断是否信任服务器
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//2.获取到受保护空间里面的证书
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//3.告诉服务器我信任你,我们建立安全通道
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
三、AFNetworking配置
NSURL * url = [NSURL URLWithString:@"https://m.baidu.com"];
AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];
dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");
requestOperationManager.completionQueue = requestQueue;
AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
//validatesCertificateChain 是否验证整个证书链,默认为YES
//设置为YES,会将服务器返回的Trust Object上的证书链与本地导入的证书进行对比,这就意味着,假如你的证书链是这样的:
//GeoTrust Global CA
// Google Internet Authority G2
// *.google.com
//那么,除了导入*.google.com之外,还需要导入证书链上所有的CA证书(GeoTrust Global CA, Google Internet Authority G2);
//如是自建证书的时候,可以设置为YES,增强安全性;假如是信任的CA所签发的证书,则建议关闭该验证,因为整个证书链一一比对是完全没有必要(请查看源代码);
securityPolicy.validatesCertificateChain = NO;
requestOperationManager.securityPolicy = securityPolicy;
对于更多详细的了解和使用可以建议大家参考一下Jamin's blog,感谢前辈的分享,让开发更高效。