AFNetworking 4.x 最全的源码说明书(三)

726 阅读10分钟

这是第三篇内容,其他内容可以看下方链接:

  • 第一部分是请求和响应的对应代码,包括AFURLRequestSerializationAFURLResponseSerialization
  • 第二部分是URLSession有关的代码,包括AFURLSessionManagerAFHTTPSessionManager
  • 第三部分是辅助的两个类:AFSecurityPolicyAFNetworkReachabilityManager
  • 第四部分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是安全的,不会被第三方串改,但是如果客户端主动信任第三方证书,那么可以通过中间人的模式来进行数据拦截,CharlesStream这类的抓包工具都是这种原理,具体自己使用这些工具按照提示体验一下就知道了。 而有一些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;

证书名称及下图中的常用名称:

证书的常用名称CN

初始化配置方法,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 的实现原理,官方文档给出的解释是:

SCNetworkReachabilityRef Overview

通过描述能够找到网络通达判定关键:一个从应用发出到网络栈中的数据包是否可以离开本地设备。关于发包、收包以及相关的处理流程可以看图:

TCP/IP分层,https://www.cnblogs.com/agilestyle/p/11394930.html

应用层调用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 大部分功能已经分析了,其他的个人随便看