前言
公司項目由HTTP轉(zhuǎn)為HTTPS椰于,需要對網(wǎng)絡(luò)請求進行自建證書驗證俐巴。主要是AFNetWorking
综芥、SDWebImage
和WKWebView
泰演。
HTTP與HTTPS
詳情參考iOS開發(fā)-網(wǎng)絡(luò)、Http與Https
AFNetWorking
詳情參考AFNetworking之于https認證搁凸。之后的主要是使用AFNetWorking
的方法進行驗證媚值。
- 自建證書驗證工具方法
static AFSecurityPolicy *securityPolicyShare = NULL;
@implementation HTTPSAuthenticationChallenge
+(AFSecurityPolicy *)customSecurityPolicy {
// 保證證書驗證初始化一次
if (securityPolicyShare != NULL) {
return securityPolicyShare;
}
// 加載證書
NSString *crtBundlePath = [[NSBundle mainBundle] pathForResource:@"Res" ofType:@"bundle"];
NSBundle *resBundle = [NSBundle bundleWithPath:crtBundlePath];
NSSet<NSData *> *cerDataSet = [AFSecurityPolicy certificatesInBundle:resBundle];
// AFSSLPinningModeCertificate使用證書驗證模式
securityPolicyShare = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:cerDataSet];
return securityPolicyShare;
}
+ (void)authenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
// 獲取服務(wù)器證書信息
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
NSURLCredential *credential = nil;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
// 基于客戶端的安全策略來決定是否信任該服務(wù)器,不信任的話护糖,也就沒必要響應(yīng)驗證
if ([[HTTPSAuthenticationChallenge customSecurityPolicy] evaluateServerTrust:serverTrust forDomain:nil]) {
// 創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建證書)
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// credential存在時使用證書驗證
// credential為nil時忽略證書褥芒,默認的處理方式
disposition = credential == nil ? NSURLSessionAuthChallengePerformDefaultHandling : NSURLSessionAuthChallengeUseCredential;
} else {
// 忽略證書,取消請求
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
if (completionHandler) {
completionHandler(disposition,credential);
}
}
@end
SDWebImage
- 創(chuàng)建
SDWebImageDownloader
的分類,在分類中進行驗證
SDWebImageDownloader
是SDWebImageView
下載圖片的核心類嫡良,在分類中重寫NSURLSession
的代理方法didReceiveChallenge
進行自建證書的驗證
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
[HTTPSAuthenticationChallenge authenticationChallenge:challenge completionHandler:completionHandler];
}
WKWebView
-
WKWebView
的驗證常規(guī)情況下在navigationDelegate
的didReceiveAuthenticationChallenge
中進行
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
[HTTPSAuthenticationChallenge authenticationChallenge:challenge completionHandler:completionHandler];
}
-
非常規(guī)情況锰扶,假如
WKWebView
展示html
里有大量圖片献酗,并且用戶點擊圖片時進行展示。- 不考慮效率
這種情況下可以在WKWebView
加載完成后使用JS
注入的方式獲取html
里圖片的src
,然后利用SDWebImage
進行預(yù)下載坷牛。這樣就會造成圖片兩次下載(網(wǎng)頁加載時下載圖片和用戶點擊展示圖片時進行下載)罕偎,浪費流量。但此方法可以讓我們正常的在navigationDelegate
的didReceiveAuthenticationChallenge
中進行證書驗證京闰。
- (void)imagesPrefetcher:(WKWebView *)webView { static NSString * const jsGetImages = @"function getImages(){\ var objs = document.getElementsByTagName(\"img\");\ var imgScr = '';\ for(var i=0;i<objs.length;i++){\ imgScr = imgScr + objs[i].src + '+';\ };\ return imgScr;\ };"; [webView evaluateJavaScript:jsGetImages completionHandler:nil]; [webView evaluateJavaScript:@"getImages()" completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSArray *urlArray = [NSMutableArray arrayWithArray:[result componentsSeparatedByString:@"+"]]; //urlResurlt 就是獲取到得所有圖片的url的拼接颜及;mUrlArray就是所有Url的數(shù)組 CGLog(@"Image--%@",urlArray); NSMutableArray<NSURL *> *tempURLs = [NSMutableArray array]; for (NSString* urlStr in urlArray) { NSURL *url = [NSURL URLWithString:urlStr]; if (url) { [tempURLs addObject:url]; } } [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:tempURLs]; }]; }
- 考慮效率
使用NSURLProtocol
進行攔截html
加載圖片的請求,由自己來進行請求并緩存圖片忙干,這樣可以避免在用點擊時展示圖片時再次請求器予,直接使用緩存圖片進行展示就行了。雖然節(jié)省了流量捐迫,提高了效率乾翔,但是我們不能夠navigationDelegate
的didReceiveAuthenticationChallenge
中進行證書驗證了,這個代理方法不會走的施戴,我們要考慮在NSURLProtocol
中進行驗證反浓。
/** WhiteList validation */ @interface NSURLRequest (WhiteList) - (BOOL)isInWhiteList; - (BOOL)is4CGTNPictureResource; - (BOOL)is4CGTNResource; @end @implementation NSURLRequest (WhiteList) - (BOOL)isInWhiteList { // 手機端非CGTN的第三方不需要驗證,也是屬于白名單的 BOOL isMobileRequest = [self.URL.host isEqualToString:@"events.appsflyer.com"] || [self.URL.host isEqualToString:@"ssl.google-analytics.com"]; if (isMobileRequest) { return YES; } // webView NSArray<NSString *> *whiteListStr = [[NSUserDefaults standardUserDefaults] objectForKey:@"kWhiteList"]; if (whiteListStr.count == 0) { return YES; } NSSet *whiteListSet = [NSSet setWithArray:whiteListStr]; // requestURL --- scheme + host NSString *requestURL = [[self.URL.scheme stringByAppendingString:@"://"] stringByAppendingString:self.URL.host]; BOOL isInList = [whiteListSet containsObject:requestURL]; // 在白名單的使用系統(tǒng)默認處理 // 不在白名單的進行攔截驗證 return isInList; } - (BOOL)is4CGTNResource { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self.URL resolvingAgainstBaseURL:YES]; BOOL isCGTNResource = [components.host containsString:@"cgtn.com"]; if (isCGTNResource) { return YES; } return NO; } - (BOOL)is4CGTNPictureResource { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self.URL resolvingAgainstBaseURL:YES]; BOOL isCGTNResource = [components.host containsString:@"cgtn.com"]; NSString *extensionName = self.URL.pathExtension; BOOL canHandle = [extensionName containsString:@"png"] || [extensionName containsString:@"jpg"] || [extensionName containsString:@"jpeg"] || [extensionName containsString:@"gif"]; if (canHandle && isCGTNResource) { return YES; } return NO; } @end static NSString *const handledKey = @"com.cgtn.www"; @interface CGTNURLProtocol ()<NSURLSessionDelegate> /** 用于圖片鏈接交由SDWebImage 管理 */ @property (strong, nonatomic) id<SDWebImageOperation> operation; /** 其他非圖片任務(wù)交由 Cache 管理, task任務(wù) */ @property (strong, nonatomic) NSURLSessionDataTask *dataTask; /** 是否為圖片鏈接 */ @property (nonatomic) BOOL isPicture; @end @implementation CGTNURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { if (!request.URL || [self propertyForKey:handledKey inRequest:request]) { return NO; } CGLog(@"canInitWithRequest----%@",request.URL); // 白名單中的鏈接不進行攔截. 默認外部處理 // CGTN 的資源強制進行驗證 return !request.isInWhiteList || request.is4CGTNResource; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return [super requestIsCacheEquivalent:a toRequest:b]; } - (void)startLoading { [self.class setProperty:@(YES) forKey:handledKey inRequest:self.request.mutableCopy]; // 攔截到的請求必須為 HTTPS if ([self.request.URL.scheme isEqualToString:@"http"]) { NSError *error = [NSError CGTNErrorWithCode:NSURLErrorAppTransportSecurityRequiresSecureConnection description:@"Deny HTTP request"]; [self.client URLProtocol:self didFailWithError:error]; return; } self.isPicture = self.request.is4CGTNPictureResource; if (self.isPicture) { __weak typeof(self) weakSelf = self; self.operation = [SDWebImageManager.sharedManager loadImageWithURL:self.request.URL options:SDWebImageRetryFailed progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (error) { [weakSelf.client URLProtocol:weakSelf didFailWithError:error]; return; } NSData *imageData = data; if (!data && image) { imageData = image.sd_imageData; } if (!imageData) { [weakSelf.client URLProtocolDidFinishLoading:weakSelf]; return; } NSURLResponse *response = [[NSURLResponse alloc] initWithURL:imageURL MIMEType:nil expectedContentLength:imageData.length textEncodingName:nil]; [weakSelf.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; [weakSelf.client URLProtocol:weakSelf didLoadData:imageData]; [weakSelf.client URLProtocolDidFinishLoading:weakSelf]; }]; return; } // 非圖片請求 NSURLSessionConfiguration *config = NSURLSessionConfiguration.defaultSessionConfiguration; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; self.dataTask = [session dataTaskWithRequest:self.request]; [self.dataTask resume]; } - (void)stopLoading { if (self.isPicture) { [self.operation cancel]; return; } [self.dataTask cancel]; } #pragma mark - NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (error) { [self.client URLProtocol:self didFailWithError:error]; } else { [self.client URLProtocolDidFinishLoading:self]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.client URLProtocol:self didLoadData:data]; } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { completionHandler(proposedResponse); } - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { [HTTPSAuthenticationChallenge authenticationChallenge:challenge completionHandler:completionHandler]; } @end
- 不考慮效率