AFNetworking实现HTTPS
在AFNetworking中,和HTTPS相关的类是AFSecurityPolicy
,和其相关的属性和方法主要有以下几个
1.validatesDomainName
这个属性声明了客户端是否去验证证书中域名这个字段,默认为YES
。
2.allowInvalidCertificates
这个属性声明了客户端是否信任不可用或者过期的证书,默认为NO
。
3.SSLPinningMode
这个属性声明了客户端如何去校验服务器端给予的证书,默认为AFSSLPinningModeNone
。
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
- AFSSLPinningModeNone 这个是默认值。当allowInvalidCertificates为YES时,客户端无条件信任服务器返回的证书,否则,会去查询iOS包含的CA根证书列表进行校验。
- AFSSLPinningModePublicKey 只验证客户端证书中包含的公钥,不验证证书的有效期等信息。
- AFSSLPinningModeCertificate 不仅验证公钥,也会验证证书本身的有效期等信息
推荐将这个属性设置为AFSSLPinningModePublicKey,因为如果证书一单过期,就需要手动更新APP进行证书的更新。一般来说,只要公钥正确就能够保证通信是安全的,因为中间人无私钥,无法解开公钥加密的数据,也就无法获取到session key。
示例:
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:cer];
securityPolicy.validatesDomainName = YES;
securityPolicy.allowInvalidCertificates = NO;
self.securityPolicy = securityPolicy;
以上方法对于实现HTTPS来说已经足够了。
自定义验证
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
AFURLSessionManager提供的setSessionDidReceiveAuthenticationChallengeBlock:
方法实际上是对NSURLSessionDelegate
的代理方法URLSession:didReceiveChallenge:completionHandler:
的封装。当走HTTPS的时候,该代理方法会收到一个challenge(质询),需要你提供认证信息才能完成连接。这时候可以通过 challenge.protectionSpace.authenticationMethod
取得保护空间要求我们认证的方式,如果这个值是 NSURLAuthenticationMethodServerTrust
的话,我们就可以插手TLS握手中“验证数字证书有效性”这一步。这个方法AFNetworking提供了默认实现,如果我们要自定义验证过程,就需要调用setSessionDidReceiveAuthenticationChallengeBlock:
方法来修改。
evaluateServerTrust:forDomain:
AFURLSessionManager提供的验证指定的ServerTrust和Domain是否可信任的方法。
ServerTrust代表服务器的SSL事务状态,可以由challenge.protectionSpace.serverTrust来拿到。
[self setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential *__autoreleasing _Nullable * _Nullable credential) {
__strong typeof(weakSelf) strongSelf = weakSelf;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
//以上调用的是系统默认实现
if (disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge) {
//证书不可用 自定义验证过程
BOOL expired = YES;
if (@available(iOS 10, *)) {
for (NSData *certData in strongSelf.securityPolicy.pinnedCertificates) {
BOOL theExpired = [strongSelf session:session cert:certData didReceiveAuthenticationChallenge:challenge];//验证证书是否全部过期
expired = expired && theExpired;
}
} else {
// SecTrustEvaluate 在iOS9 及以下会发生crash,暂停使用
expired = NO;
}
// 证书全部过期,提示升级
if (expired) {
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf handleSSLError:handler overdue:YES];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^(void){
[strongSelf handleSSLError:handler overdue:NO];
});
}
}
return disposition;
}];