这是第三篇内容,其他内容可以看下方链接:
- 第一部分是请求和响应的对应代码,包括
AFURLRequestSerialization
、AFURLResponseSerialization
- 第二部分是URLSession有关的代码,包括
AFURLSessionManager
和AFHTTPSessionManager
; - 第三部分是辅助的两个类:
AFSecurityPolicy
和AFNetworkReachabilityManager
; - 第四部分是
UIKit+AFNetworking
目录下的有趣内容。
AFSecurityPolicy类
这个类主要是用来核实服务器是否受信的,一般我们在配置app的https请求时会涉及到。 至于这个类中涉及到的加密解密、签名验证、证书钥匙串这些概念,可以看苹果的官方文档,写的非常透彻浅显。 我们看一下这个类的声明:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, // 不使用ssl验证
AFSSLPinningModePublicKey, // 使用public key进行验证
AFSSLPinningModeCertificate, // 使用证书验证
};
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
三种类型对应不同的核实策略,具体放到后面来说,一般我接触到的大多数app都使用的默认模式,即AFSSLPinningModeNone
,使用这个模式图省事,但是并不安全,可以被抓包。
通常来说https是安全的,不会被第三方串改,但是如果客户端主动信任第三方证书,那么可以通过中间人的模式来进行数据拦截,
Charles
、Stream
这类的抓包工具都是这种原理,具体自己使用这些工具按照提示体验一下就知道了。 而有一些app即使配置了第三方证书也不能抓包的主要原因就是核实模式的问题,正常的核实模式是使用本地钥匙串中的证书,这个证书可以是权威机构颁布的,同样也可以是用户自己信任的,称为anchor certificate
,只要anchor certificate
核实通过即认为是受信任的。 这些无法抓包的app并没有使用本地钥匙串中的anchor certificate
,而是使用客户端本地的证书,我们可以称这种证书为pinned certificate
,将pinned certificate
作为anchor certificate
进行信任核实,就规避了抓包工具这种第三方的节点替换。 一些数据敏感型的app一定会使用pinned certificate
来进行信任核实的。
接下来三个属性可以放在一起说,
// 上面的讲解中提到过这个pinned certificates,不再赘述
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否允许非法的证书,如若同时设置了AFSSLPinningModeNone,那么验证的时候就会无脑信任了
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证证书名称
@property (nonatomic, assign) BOOL validatesDomainName;
证书名称及下图中的常用名称:
初始化配置方法,default
对应的是AFSSLPinningModeNone
:
+ (instancetype)defaultPolicy;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode
withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
整个AFSecurityPolicy
类中,除了一些工具方法之外,最重要最核心的方法就是核实方法:
// 第一个参数是Security库中的trust对象,一般来讲这个值可以从身份认证请求中获取到,或者手动创建
// 第二个参数是用来核实常用名称的domain,这个值可以为空
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;
关于Trust(
SecTrustRef
)对象做一些解释:Trust对象是TLS验证的执行对象。 这个对象内部包含了Policy(SecPolicyRef
)对象,Policy对象可以提供需要进行核实的domain; 还包含了一个anchor certificates
的数组,当核实的时候会从这些anchor certificates
进行匹配。
手动生成trust对象的方法,可以参考AF中的测试用例:
SecTrustRef AFUTTrustChainForCertsInDirectory(NSString *directoryPath) {
NSArray *certFileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil];
NSMutableArray *certs = [NSMutableArray arrayWithCapacity:[certFileNames count]];
for (NSString *path in certFileNames) {
// 获取data格式的certificate
NSData *certData = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:path]];
// 创建certificate对象
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certData));
[certs addObject:(__bridge_transfer id)(cert)];
}
// 设置policy为默认的x.509
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust = NULL;
// 使用certificate数组和policy对象来创建trust对象
SecTrustCreateWithCertificates((__bridge CFTypeRef)(certs), policy, &trust);
CFRelease(policy);
return trust;
}
// 可以搭配public method来获取bundle中的certificate
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
了解了trust
对象之后,我们具体看一下evaluateServerTrust
方法的实现:
- (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];
// 根据validatesDomainName属性设置,如果需要验证domain,那么使用SecPolicyCreateSSL生成具有domain标识的policy对象,否则使用默认的x.509
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 设置trust对象的policy
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 如果mode == none,会先判断是否允许非法证书,如果不允许,就直接去核实trust对象
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
}
// 如果是其他两种mode,不许非法证书且核实trust对象不通过,则直接返回NO
else if (!self.allowInvalidCertificates && !AFServerTrustIsValid(serverTrust)) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeCertificate: {
// certificate mode下,会从我们设置好的pinned certificate数据转成certificate类型
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 设置当前的anchor certificate为我们设置到的证书,单独调用这个方法会忽略本地的其他证书,只使用我们的pinned certificate核实
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
// 进行核实trust
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// 通过验证之后,获得证书链,这个链包含了所有验证过去的证书。
// 可以这样理解,我们的pinned certificate可能是叶子证书,也可能是中间证书
// 在我们去核实的时候,会从需要验证的证书开始,遍历每一个证书,将这个证书和pinned certificate比对
// 比对完全一致则直接通过信任,否则会往证书的上级签发进行验证,直到找到一个包含的
// 那么整个过程涉及到的证书就属于这个证书链
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
// 找到证书链之后,倒序遍历,确保pinned certificate是在证书链中
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 这个方法就是获取trust对象中所有证书的public key,具体逻辑没什么好说的
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
// 将服务端的所有公钥对象和pinned certificate对应的所有公钥进行循环比对,只要存在相同的公钥即认为通过信任,同样是遍历证书链
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
default:
return NO;
}
return NO;
}
以上大致对和新方法evaluateServerTrust
进行了说明,其中有很多静态方法无非就是一些certificate
或者public key
生成,就不赘述了,可以看一下其中的核实方法AFServerTrustIsValid
:
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
#pragma clang diagnostic pop
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
方法实现没什么太好说的,就是调用Security
库中的SecTrustEvaluate
方法,进入内部逻辑验证。值得一提的就是SecTrustEvaluate
方法说明中,强调了这个方法是同步网络访问以获取中间证书或执行吊销检查的,也就是需要在异步线程去执行,而AFServerTrustIsValid
方法本身并没有异步执行,究其原因是因为URLSession
中的认证挑战回调是异步线程:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
所以在日常单独使用的时候要注意这一点,Security
库中提供了异步方法SecTrustEvaluateAsync
,同时,这两个方法在iOS13以后会报警告,建议iOS12之后的应用使用新方法SecTrustEvaluateWithError
。
上面这一小段属于看源码时候的额外发现,至于具体用哪个方法,还是要看AFNetworking
的核心人员了...
其他场景下的时候用法,可以看一下这个类的单元测试AFSecurityPolicyTests
。
AFNetworkReachabilityManager类
网络通达检测工具,官方说明上的使用方法:
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
比较简单明了,设置回调并开启监控,状态改变时通过函数来返回具体描述:
NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWWAN:
return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWiFi:
return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
case AFNetworkReachabilityStatusUnknown:
default:
return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
}
}
现在,我们看一下这个类具体是怎么实现的。先看下声明的属性:
@interface AFNetworkReachabilityManager ()
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
@end
我们可以看到最关键的属性SCNetworkReachabilityRef
,AFNetworkReachabilityManager
就是基于这个类进行的封装,具体这个类的使用方式可以看一下官方文档。这里只看一下SCNetworkReachability
的实现原理,官方文档给出的解释是:
通过描述能够找到网络通达判定关键:一个从应用发出到网络栈中的数据包是否可以离开本地设备。关于发包、收包以及相关的处理流程可以看图:
应用层调用socket api
发送一段数据,通过传输层、网络层的协议栈增加对应头之后,由链路层作为网络驱动,送达到物理层,最后网卡发送数据,这大致就是网络发包的粗略流程,如流程图右侧所示(左侧是网络收包的流程展示)。而SCNetworkReachability
就是利用网络发包来确认网络状态的。
了解基本原理之后,我们从一开始的使用方式上入手,在sharedManager
方法中,调用了以下方法来初始化socket
地址:
+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
struct sockaddr_in6 address; // 定义IPv6的socket地址结构
bzero(&address, sizeof(address)); // 将地址结构清零
address.sin6_len = sizeof(address); // 设置地址长度
address.sin6_family = AF_INET6; // 设置family为IPv6
#else
...
#endif
return [self managerForAddress:&address];
}
+ (instancetype)managerForAddress:(const void *)address {
// 使用socket地址来初始化SCNetworkReachabilityRef
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
// 初始化AFNetworkReachabilityManager
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
这块代码没什么好说的,注意对SCNetworkReachabilityRef
的持有和释放。单例初始化之后,就是使用setReachabilityStatusChangeBlock
方法将回调赋值给networkReachabilityStatusBlock
,这个回调的定义:
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
再之后就是startMonitoring
开启监控的方法了,这是这个类的核心方法:
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
// 声明用于SCNetworkReachability的回调,这个回调会把self返回,方便内联函数中使用,当回调触发,会更新当前status,并通知外界使用方。
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
return strongSelf;
};
// 初始化SCNetworkReachability的上下文,传入持有和释放的回调函数。
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
// 设置SCNetworkReachability对象,以及状态改变时的触发函数
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
// 将监视任务加到runloop中以便长时间监控,这里加入的是主线程runloop
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
// 加入runloop之后,在全局队列中以最低的优先级触发一次当前网络状态的查询
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
// 主动获取网络状态
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
// 触发外部回调
AFPostReachabilityStatusChange(flags, callback);
}
});
}
整个方法里面就是调用SCNetworkReachability
相关接口来设置回调、加入runloop等一系列操作。SCNetworkReachability
相关接口我们不做过多介绍,其中的触发回调AFNetworkReachabilityCallback
函数内部调用了AFPostReachabilityStatusChange
函数,这个函数主要作用就是将状态flag解析并传到外部:
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusCallback block) {
// 根据SCNetworkReachability中传入的flag来判断当前的网络状态,涉及到SCNetworkReachabilityFlags枚举下的一些类型,不展开介绍
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
// 保存更新单例类状态,并进行外部回调,主线程队列的目的是为了保证执行顺序
dispatch_async(dispatch_get_main_queue(), ^{
AFNetworkReachabilityManager *manager = nil;
if (block) {
manager = block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:manager userInfo:userInfo];
});
}
至此AFNetworkReachabilityManager
大部分功能已经分析了,其他的个人随便看