AFNetworking框架下Https请求失败的一些总结(一)

最近公司项目的合作方陆续升级为https,本来是一件可喜可贺的事,但是乐极生悲,总有一些幺蛾子不那么让人省心...

ERROR:NSURLErrorDomain error code -999

一、报错信息解释

定义:NSURLErrorCancelled = -999

即:从定义很容易看出,该错误说明当前请求被取消了。

二、报错原因

为什么会被取消呢?

理解一个东西:
NSURLSessionAuthChallengeDisposition (如何处理证书)

    NSURLSessionAuthChallengeUseCredential = 0, 使用该证书 安装该证书    
    NSURLSessionAuthChallengePerformDefaultHandling = 1, 默认采用的方式,该证书被忽略  
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 取消请求,证书忽略 
    NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒绝

再看一段代码:

AFNetworking框架下Https请求失败的一些总结(一)_第1张图片
AFNetworking代码片段

不难看出,如果evaluateServerTrust:forDomain方法返回NO,该请求就会被取消掉。下面是源码(已加注释):

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
/*
1. allowInvalidCertificates:是否允许使用自建证书(服务器自己生成的CA证书)。
2. validatesDomainName:是否需要验证domain。如果你想验证自建证书的domain是否有效。那么你必须使用pinnedCertificates并且SSLPinningMode不为AFSSLPinningModeNone才可以。
3. SSLPinningMode:证书验证方式。
   3.1. AFSSLPinningModeNone:表示不使用SSL pinning,无条件信任服务器证书。
   3.2. AFSSLPinningModeCertificate:验证服务器返回证书和本地证书的所有部分。
   3.3. AFSSLPinningModePublicKey :验证服务器返回证书和本地证书中的PublicKey部分。
4. 所以当客户端允许自建证书且需要验证域名时,没有导入的pinnedCertificates或者SSLPinningMode == AFSSLPinningModeNone,均表示无法验证该自建证书。所以都返回NO。
*/
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
        //  According to the docs, you should only trust your provided certs for evaluation.
        //  Pinned certificates are added to the trust. Without pinned certificates,
        //  there is nothing to evaluate against.
        //
        //  From Apple Docs:
        //          "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
        //           Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
        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. 方法一:对于一些没有自己SDK的合作第三方,目前部分是采用自建证书的方式,且更新至https是分阶段进行,所以不可能及时将证书拷贝给到用户,所以此时必须在项目中配置“允许自建”、“不需要验证域名”等,否则请求会被取消,出现题中错误。答案来源
manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.validatesDomainName = NO;

或者

manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.SSLPinningModep == AFSSLPinningModeNone;
  1. 方法二:
    其余一些可能性
    AFNetworking框架下Https请求失败的一些总结(一)_第2张图片
    来自github

四、资料参考

图解SSL/TLS协议
SSL/TLS协议运行机制的概述
正确使用AFNetworking的SSL
AFNetworking源码解析

你可能感兴趣的:(AFNetworking框架下Https请求失败的一些总结(一))