对AFNetworking进行解耦 AFNetworking解耦后能够分为如下几个模块: 1. NSURLSession:主要的一个基于NSURLSession的管理模块; 2. Reachability:网络监测模块; 3. Security:Https验证模块; 4. Serialization:序列化模块,包含了请求和响应的序列化; 5. UIKit:包含了一些UI的扩展,方便调用。api
NSURLSession 和 Reachability 还有 Serialization 、UIKit 这些模块就不作过多的解析了,重点了解一下 Security中Https的认证。浏览器
里面有关键的一步:即客户端验证服务端返回的公钥证书,这就是https的验证:安全
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
复制代码
只要是Https的请求,就会走到这个方法,该方法的做用就是处理服务器返回的证书, 须要在该方法中告诉系统是否须要安装服务器返回的证书,安装即表明公钥证书验证经过。bash
咱们来经过AFNetworking的源码来查看一下它是怎么处理的,源码里有详细注释:服务器
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//NSURLAuthenticationChallenge 须要处理的挑战,里面包含了一个类型为 NSURLProtectionSpace 的属性
//NSURLProtectionSpace里面包含了 host port authenticationMethod等属性,还有服务器用于https验证的一些信息好比公钥、random_s等,这些都是服务器传过来的,authenticationMethod表示受权方式,对于AFNetworking来讲,是经过比较NSURLAuthenticationMethodServerTrust来决定是否须要证书验证
/* 处理方式
NSURLSessionAuthChallengeUseCredential:使用指定证书
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
NSURLSessionAuthChallengeRejectProtectionSpace:拒绝挑战,并尝试下一个验证保护空间,忽略证书参数
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
/* NSURLCredential为一个证书,它决定了咱们如何处理这个挑战,它的初始化方法有:
//通常用于须要处理身份验证401的错误验证
- (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence
//经过钥匙串中取得一个客户端证书来建立证书,通常用于服务端进行双向认证
+ (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
//经过服务端传回的SecTrustRef(包含待验证的证书和支持的验证方法等)对象建立证书,通常客户端须要验证服务端身份时使用,即Https中的客户端对服务端证书验证这一步,收到服务端的challenge,例如https须要验证证书等 ats开启
- (instancetype)initWithTrust:(SecTrustRef)trust
*/
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
//若是须要自定义处理当前的挑战,那么就经过block回调回去
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
//挑战凭证不必定都是进行HTTPS证书的信任,也多是须要客户端提供用户密码或者提供双向验证时的客户端证书。当这个挑战凭证被验证经过时,请求即可以继续顺利进行
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//开始验证证书 ,只须要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方,其余的认证方式,只能由咱们自定义Block的实现
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//验证成功后,经过该证书建立一个NSURLCredential ,该证书会被传给服务端
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//肯定挑战方式
if (credential) {
//证书挑战
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默认挑战
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//验证失败,代表客户端没办法处理,那么就Cancel掉
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
//默认挑战
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
//将验证完成的证书发给服务器
completionHandler(disposition, credential);
}
}
复制代码
下面跳入真正作验证的方法: 先来认识几个属性:网络
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, //表明无条件信任服务器的证书,这个模式表示不作SSL pinning,只跟浏览器同样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会经过,如果本身服务器生成的证书,这里是不会经过的。
AFSSLPinningModePublicKey, //表明会对服务器返回的证书中的PublicKey进行验证,客户端要有服务端的证书拷贝,验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通讯不会被窃听,由于中间人没有私钥,没法解开经过公钥加密的数据。
AFSSLPinningModeCertificate, //表明会对服务器返回的证书同本地证书所有进行校验,须要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
};
复制代码
上一步生成证书的方法为:session
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//serverTrust为SecTrustRef类型的数据,就是待验证的对象。
复制代码
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//若是有域名 设置了容许信任无效或过时证书 须要验证域名 没有提供证书或者不验证证书 返回NO
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
return NO;
}
//用来装验证策略
NSMutableArray *policies = [NSMutableArray array];
//生成验证策略。若是要验证域名,就以域名为参数建立一个策略,不然建立默认的basicX509策略
if (self.validatesDomainName) {
// 若是须要验证domain,那么就使用SecPolicyCreateSSL函数建立验证策略,其中第一个参数为true表示为服务器证书验证建立一个策略,第二个参数传入domain,匹配主机名和证书上的主机名
//1.__bridge:CF和OC对象转化时只涉及对象类型不涉及对象全部权的转化
//2.__bridge_transfer:经常使用在讲CF对象转换成OC对象时,将CF对象的全部权交给OC对象,此时ARC就能自动管理该内存
//3.__bridge_retained:(与__bridge_transfer相反)经常使用在将OC对象转换成CF对象时,将OC对象的全部权交给CF对象来管理
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 若是不须要验证domain,就使用默认的BasicX509验证策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 为serverTrust设置验证策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//不作SSL pinning,只在系统的信任机构列表里验证服务端返回的证书
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//若是验证无效AFServerTrustIsValid,并且allowInvalidCertificates不容许自签,返回NO
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default: //这里上面已经判断过了,执行到这里的话直接返回NO
return NO;
//客户端把服务端证书拷贝,存放在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
//所有校验(nsbundle .cer)
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates作一些处理,保证返回的证书都是DER编码的X.509证书
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 将pinnedCertificates设置成须要参与验证的Anchor Certificate(锚点证书,经过SecTrustSetAnchorCertificates设置了参与校验锚点证书以后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书自己,则信任该证书),具体就是调用SecTrustEvaluate来验证
//serverTrust是服务器来的验证,有须要被验证的证书
// 把本地证书设置为根证书,
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//再去验证证书
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//注意,这个方法和咱们以前的锚点证书不要紧了,是去从咱们须要被验证的服务端证书,去拿证书链。
// 服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
// 全部服务器返回的证书信息
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
//若是咱们的证书中,有一个和它证书链中的证书匹配的,就返回YES
// 是否本地包含相同的data
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
//只验证公钥
NSUInteger trustedPublicKeyCount = 0;
// 从serverTrust中取出服务器端传过来的全部可用的证书,并依次获得相应的公钥
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
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
复制代码