iOS-AFNetworking底层框架解析

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的框架组成:
    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流程

    总的来说,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的网络图片加载有什么区别呢?这里留个悬念,小伙伴们也可以先去探索下哦,记得分享哈同时@一下我哟。后面如果有空余时间,会专门开一篇博文或者在这篇末尾续上他们的区别对比,敬请关注吧

今天就到这里,忙碌了一天,大家洗洗睡觉吧~ ~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342