写这篇文章的目的
之前在做一个奇葩的定制,遇到提示“无法验证服务器的身份,可能链接到伪装服务器”的错误,然而我自己这边的demo没有出现这个问题,一度表示很无奈。还有一个是使用SDWebImage加载网络图片时,图片下载失败(图片地址也是https),还有一个是使用UIImageView+AFNetworking加载图片时,请求被cancel。跟客户沟通了很久,才解决了以上问题,所以想写篇文章记录一下。
什么是SSL
SSL证书是数字证书的一种,也称为SSL服务器证书,SSL证书通过在客户端浏览器和Web服务器建立一条SSL安全通道,实现数据信息在客户端和服务器之间的加密传输,可以防止数据信息的泄露,保证了双方传递信息的安全性,而且用户可以通过服务器证书验证他所访问的网站是否是真实可靠。
苹果ATS对SSL做了以下要求:
- 使用SHA2级别的证书签名算法,例如SHA-256, SHA-512等;
- 证书公钥算法使用RSA 2048位及以上,或使用更高的算法ECC 256加密算法;
从权威机构认证过的证书一般都符合苹果ATS要求,自建证书基本都需要APP去绕过证书校验的步骤。符合要求的很好处理,接下来说说如何绕过证书校验。
iOS绕过https证书校验
UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (!_authenticated) {
_authenticated = YES;
_urlConnection = [[NSURLConnection alloc] initWithRequest:_requestW delegate:self];
[_urlConnection start];
return NO;
}
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] == 0){
_authenticated = YES;
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
_authenticated = YES;
[self loadRequest:_requestW];
[_urlConnection cancel];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
但是下面代理方法已经废弃
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge API_DEPRECATED("Use -connection:willSendRequestForAuthenticationChallenge: instead.", macos(10.2,10.10), ios(2.0,8.0), watchos(2.0,2.0), tvos(9.0,9.0));
在这次定制中,就是因为没有进入下面两代理方法,导致客户那边无法打开app内嵌到网页,所以要用下面的方法代替
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
SDWebView
这个问题没有解决,但是代码中已经设置了绕过证书校验,
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
这个方法中options配置为SDWebImageAllowInvalidSSLCertificates(信任所有证书),在SDWebImageDownloader中也对证书校验进行了处理,
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
}
真正的处理是在SDWebImageDownloaderOperation中进行,跟webView的处理方式是一样的。
- (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 {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
AFNetworking
AF是因为AFImageDownloader中AFHTTPSessionManager没有设置securityPolicy,只要在initWithSessionConfiguration:
方法中添加sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
就可以绕过证书校验。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
以上就是这次踩完坑后的收获,有什么不对的地方还请路过的朋友们指点。