大量的App现在都用优秀的图片处理框架 SDWebImage 来展示图片内容,由于现在 Apple 对 app 的安全管控开始越来越严格。 Apple 的做法是从管控开发者的角度来提升用户的安全度,普及 https 的措施一定会逐渐全面覆盖。
实际开发当中,我们的网络请求模块,除非 App 内部有下载资源的模块,不然我们很少会将自己的 App 内部的图片做特殊处理,在实际开发过程中,网络模块和 SDWebImage 联合起来才算一个完整的网络架构模块,而 SDWebImage 本身是有校验证书方法的,只不过内部没有具体的操作,也没有详细写,每个开发者可以根据具体业务需要根据实际情况来处理,所以对 SDWebImage 框架在哪里修改,在哪里改写来成功完成 https 的请求,就变成了一个事儿。
其实只要理解了SDWebImage框架的底层,以及几个关键类的作用,这个问题便不是问题:
对于 SDWebImage 框架下的 SDWebImageDownloaderOperation 这个类中的这个方法重写:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
个人建议不要修改原框架的原文件,无破坏性无入侵式修改的话,对 SDWebImageDownloaderOperation 这个类写一个分类,然后重写上述方法,以下是具体重写内容。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
//改写方法 然后插入证书验证代码
do
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
NSCAssert(serverTrust != nil, @"serverTrust is nil");
if(nil == serverTrust) break;
/* failed */
/**
* 导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"oss-ifeixiu-com" ofType:@"cer"];
//自签名证书
NSData *caCert = [NSData dataWithContentsOfFile:cerPath];
NSCAssert(caCert != nil, @"caCert is nil");
if(nil == caCert) break;
/* failed */
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
if(nil == caRef) break;
/* failed */
//可以添加多张证书
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
if(nil == caArray) break;
/* failed */
//将读取的证书设置为服务端帧数的根证书
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
if(!(errSecSuccess == status)) break;
/* failed */
SecTrustResultType result = - 1;
//通过本地导入的证书来验证服务器的证书是否可信
status = SecTrustEvaluate(serverTrust, &result);
if(!(errSecSuccess == status)) break;
/* failed */
debug_NSLog(@"stutas:%d",(int)status);
debug_NSLog(@"Result: %d", result);
BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);
if (allowConnect) {
debug_NSLog(@"success");
}else {
debug_NSLog(@"error");
}
/* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
if(!allowConnect) {
break;
/* failed */
}
#if 0
/*
Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success
*/
/* since the user will likely tap-through to see the dancing bunnies */
if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError) break;
/* failed to trust cert (good in this case) */
#endif
// The only good exit point
debug_NSLog(@"信任该证书");
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
while(0);
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}