AFNetworking是iOS开发中一个常用的网络请求第三方框架,我们常会这样子去发起一个请求
NSString *url = [NSString stringWithFormat:@"https://test?type=%@",@"test"];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:url parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"success = %@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"failure = %@",error);
}];
很多时候也由于各种原因,没能细细的去总结它整个网络请求的过程,趁着今天有点闲暇时间,我们一起来看看吧。
还是和上一篇SDWebImage底层框架解析一样,我先来看看它在GitHub上是怎么解释的:
AFNetworking is a delightful networking library for iOS, macOS, watchOS, and tvOS. It's built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
AFNetworking是一个非常适合iOS、macOS、watchOS和tvOS的网络库。它构建在Foundation URL加载系统之上,扩展了内置在Cocoa中的强大的高级网络抽象。它有一个模块化的架构,拥有精心设计的、功能丰富的api,使用起来很有趣。
大体来讲,就是上面字面上的意思啦,总结来讲就三个:网络库,高级网络抽象,丰富的api。
在写这篇博客之前,我在想,以一种怎样的方式能更加友好的去阐述它一些原理性的东西,思来想去,我们还是打开代码一起跟着浏览吧,或许,代码大家更有眼缘些,嘎嘎
- 框架组成
我们一起来看看AFNetworking的框架组成:
从上面看,分为五大块,而AFURLSessionManager为AFNetworking的核心类,AFNetworking所展开的工作都会围绕这个类来进行,同样,AFNetworking有一个子类继承了它,这个类是AFHTTPSessionManager,它主要负责我们当前的业务逻辑处理,它们倆构成了第一大块;而对于请求的时候,我们需要做序列化,Serialization文件夹里就包含了我们请求和相应的序列化,这个序列化对于我们请求来说,它可能主要用来参数的拼接,header,parameters,多表单参数的提交,而对于当前的response,它取决于我们怎么去序列化返回来的数据,是以二进制,json,xml,它都有不同的类去实现具体的功能,总的来说,Serialization这个文件夹里的类是处理序列化的,这个序列化分为请求和响应的序列化,这也就是第二部分;那第三部分是什么?我可以看到Serialization文件夹上面还有俩,其中一个是Reachability,里面有AFNetworlingReachabilityManager这个类,相信大家对于这个应该不陌生了吧,我们常常在开发中会有网络状态监测的需求,喏,这个是类就是用来做网络检测的。除了Reachability这个文件夹,另一个是Security,也就是第四部分,它是用来做安全认证的,比如证书的一些校验逻辑就是在这里面啦。最后一部分,就是UIKit那个文件加了,它是一个简单的扩展,它用来便利的提供我们的UI层来调用我们的业务逻辑。
理解上面的几个模块的时候,可以结合AFNetworking框架组成图一起阅读。
看到这里,是不是对AFNetworking有一个大概的了解了,有点迫不及待的想去探究更深入的层面,别急,这里我们先开个“题”,先说说HTTPS的流程,方便后面讲解的扩展。
-
HTTPS的流程
总的来说,HTTPS的流程分为四阶段
第一阶段:Client Hello
第二阶段:Server Hello - Server Done -> 验证当前 Server Trust
第三阶段:证书效验(客户端进行)
第四阶段:客户端产生Pre-master(预主秘钥)这个是怎么生成的?是苹果的框架CFNetWork通过随机数计算来的,具体怎么实现,我也没有研究过哈
最后就是数据的通信啦。
版本信息:包含了随机数(random_c/random_s),支持的加密算法等信息,通过TCP发送给服务端;
非对称加密(RSA):客服端拿到公钥进行加密,服务器用私钥进行解密 ;
对称加密:客户端和服务端用同一把秘钥进行加密和解密,叫做对称加密
备注:非对称加密安全,但非常耗时,性能低,一般用于验证HTTPS,当HTTPS建立完成之后,我们就可以用对称加密来进行通讯,也就保证我们的数据安全性了。
那这里也许有人会问,如果拿到公钥,那数据是不是就不安全了?
答案:对称机密的秘钥是用 random_c + random_s + Pre-master生成的,用公钥加密后发送给服务端的,而我们进行通讯使用的秘钥是客户端和服务端的随机码加上预主秘钥产生的,所以,不会有问题的啦。
- 证书校验
那HTTPS流程讲完了,但你有没有主要到一个问题,客户端是怎么进行证书效验的?AFNetworking是怎么处理的?
开始进入高潮啦,啊哈哈,飘了飘了
我们一起打开代码来看看吧,看到代码,懵了,我去,怎么下手,去哪里看它的处理呢?别急,前面说到了,AFURLSessionManager是AFNetworking的核心类,它所展开的工作都是围绕这个来进行的,那我们就从这个类开始看起,看看是不是真的如所说的,它是一个管理控制中心类。
大概浏览了下代码,找到NSURLSession的代理,发现,ANetworking好像在这里做了处理,也许你会问,到底是不是哦?那我们一起开分析下代码:
//收到服务端的challenge,例如https需要验证证书等
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
我去,这就没了,讲清楚啊,给这一段代码啥意思嘛?哎呦喂,慢慢来丫
这里有几个点要说一下,
- tip1
[challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]
我们需要重delegate回来的challenge去查找接受服务挑战的方式是否就是信任证书这种方式。我们注意到challenge的类型是(NSURLAuthenticationChallenge *),点进去可以看到,里面找到下面的代码
/*!
@abstract Get a description of the protection space that requires authentication
@result The protection space that needs authentication
*/
@property (readonly, copy) NSURLProtectionSpace *protectionSpace;
AFNetworking自己也说了,这是一个需要认证的保护空间,那我们再点进去看下,这回我们就能看到了
/*!
@abstract Get the authentication method to be used for this protection space
@result The authentication method
*/
@property (readonly, copy) NSString *authenticationMethod;
以上,就有了接收服务器挑战的方法的判断!
那既然它的authenticationMethod是NSURLAuthenticationMethodServerTrust,接下来就是验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现)
我们看到这样一行代码
- tip2
[self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]
self.securityPolicy -> ?
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
前面,我们也说到过,Security是用来做安全认证的,那这里是不是来验证服务端是否值得信任的?(知识点慢慢串起来了哈)我们点进去看看:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
(1)
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {...}
如果有服务器域名、设置了允许信任无效或者过期证书(自签名证书)、需要验证域名、没有提供证书或者不验证证书,返回no
(2)
NSMutableArray *policies = [NSMutableArray array];
装验证策略
(3)
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
生成验证策略。如果要验证域名,就以域名为参数创建一个策略,否则创建默认的basicX509策略
(4)
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
为serverTrust设置验证策略,用策略对serverTrust进行评估
(5)
//如果是AFSSLPinningModeNone(不做本地证书验证,从客户端系统中的受信任颁发机构 CA 列表中去验证服务端返回的证书)
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//不使用ssl pinning 但允许自建证书,直接返回YES;否则进行第二个条件判断,去客户端系统根证书里找是否有匹配的证书,验证serverTrust是否可信,直接返回YES
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//如果验证无效AFServerTrustIsValid,而且allowInvalidCertificates不允许自签,返回NO
return NO;
}
看上面注释
(6)
case AFSSLPinningModeCertificate: {...}
这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝,而客户端保存的证书存放在self.pinnedCertificates中。
总的来说,这里面所做的操作就是将本地的证书设置为根证书,然后评估指定证书和策略的信任度,如果为可信的,我们再去遍历服务端的证书链,查看是否有与本地证书匹配的,如果有,则返回YES,否则返回NO。
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//把证书数据类型NSData -> SecCertificateRef ,并添加进数组里
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 把本地证书设置为根证书,
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//评估指定证书和策略的信任度(由系统默认可信或者由用户选择可信)
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//遍历证书
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
//如果我们的证书中,有一个和它证书链中的证书匹配的,就返回YES
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
//没有匹配的
return NO;
}
(7)
case AFSSLPinningModePublicKey: {...}
这个就是公钥验证啦,同理,这里也需要有服务端的证书拷贝,才能进行证书公钥验证。
总的逻辑是:从serverTrust拿到服务端所有可用证书的公钥,查看服务端的公钥有没有与本利公钥相同的,如果有,则验证通过,否则反之。
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
//从serverTrust中拿到服务器端传过来的所有可用证书的公钥
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//遍历服务端公钥
for (id trustChainPublicKey in publicKeys) {
//遍历本地公钥
for (id pinnedPublicKey in self.pinnedPublicKeys) {
//判断如果相同trustedPublicKeyCount+1
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
以上就是这个方法【- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain】里的所有逻辑啦
差点忘了,这里还有一个小点
(8)
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {...}
验证服务器是否可信任的?
重点就是这个方法了【__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);】
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
- tip3
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//确定挑战的方式
if (credential) {
//证书挑战
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默认挑战
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
如果信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务,我信任你,你给我发送数据吧。如果信任评估没有通过,则取消挑战。
总的来说,HTTPS证书效验的过程就是这样子的啦。
1、证书链:是根证书以及根证书颁发的子证书组成的一系列证书链
2、
NSURLSessionAuthChallengeDisposition - 挑战处理类型
NSURLSessionAuthChallengeUseCredential:使用指定的证书
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间;忽略证书参数
讲到这里也差不多结束了。
那最后的最后,还有一个要提一下,我们知道,AFNetworking也有网络图片加载的方法,它和SDWebImage的网络图片加载有什么区别呢?这里留个悬念,小伙伴们也可以先去探索下哦,记得分享哈同时@一下我哟。后面如果有空余时间,会专门开一篇博文或者在这篇末尾续上他们的区别对比,敬请关注吧
今天就到这里,忙碌了一天,大家洗洗睡觉吧~ ~