自蘋果宣布2017年1月1日開端強迫運用https以來,htpps漸漸成為大家討論的對象之一,不是說此前https沒有呈現,只是這一決策讓得開發者始料未及,博主在15年的時分就做過https的接口,深知此坑之深,緣由就是本身對這方面知識不理解加上網上的材料少,除此外還有博客不知對錯就相互轉載,招致事先網上簡直找不到能用的代碼,這一點,博主說的毫不誇大。
鑒於此,博主不斷想填一下這個坑,多添加一些正確的代碼,來供廣闊開發者運用,後來不斷被放置,經過嘗試後,博主現將整理好的代碼發布在這裡,希望能幫到著急尋覓的開發者。
1.先來說說老的A.networking2.x怎樣來完成的
博主在網上看過幾篇帖子,其中說的一些辦法是正確的,但是卻並不全對,由於那幾篇博客簡直一樣,博主不能確定最早的那篇是誰寫的,所以就重新在上面闡明下辦法:
1)倒入client.p12證書;
2)在plist文件做如圖配置:
3)在A.networking中修正一個類:
找到這個文件,在外面添加一個辦法:
- (OSStatus)extractIdentity:(CFDataRef)inP12Data toIdentity:(SecIdentityRef*)identity { OSStatus securityError = errSecSuccess; CFStringRef password = CFSTR("證書密碼"); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); securityError = SecPKCS12Import(inP12Data, options, &items); if (securityError == 0) { CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0); const void *tempIdentity = NULL; tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity); *identity = (SecIdentityRef)tempIdentity; } if (options) { CFRelease(options); } return securityError; }
再修正一個辦法:
用上面的這段代碼交換NSURLConnectionDelegate中的同名代碼,
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]; //倒入證書 NSLog(@"thePath===========%@",thePath); NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; SecIdentityRef identity = NULL; // extract the ideneity from the certificate [self extractIdentity :inPKCS12Data toIdentity:&identity]; SecCertificateRef certificate = NULL; SecIdentityCopyCertificate (identity, &certificate); const void *certs[] = {certificate}; // CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); // create a credential from the certificate and ideneity, then reply to the challenge with the credential //NSLog(@"identity=========%@",identity); NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:nil persistence:NSURLCredentialPersistencePermanent]; // credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; }
4)發起懇求
NSString *url = @"xxxxxxxxxx"; // 1.取得懇求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; //2設置https 懇求 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; securityPolicy.alloWinvalidCertificates = YES; mgr.securityPolicy = securityPolicy; // 3.發送POST懇求 [mgr POST:url parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responSEObject) { NSLog(@"responSEObject: %@", responSEObject); } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) { NSLog(@"Error: %@", error); }];
到此,老版的A.networking懇求https接口的雙向驗證就做完了,但是有一個問題,這裡需求改動AFNetworking的代碼,何況新的AFNetworking曾經有了,為了堅持代碼的生機,老的應該摒棄的,而且更新pods後一定交換的代碼就沒了,也是一個問題,不要急,上面來說說怎樣用新的AFNetworking,並處理被pods更新交換代碼的問題。
最後再說一點,運用老的AF來懇求,只用到了client.p12文件,並沒有用到server.cer,在新的外面是有用到的,猜想能夠是客戶端選擇信任任何證書招致的,就變成了單向的驗證。
Demo放在最後
2.來說說新的AFNetworking3.x怎樣來完成的
1)倒入client.p12和server.cer文件
2)plist內的設置,這是和下面一樣的:
3)這裡可不需求修正類外面的代碼,但是這裡需求重寫一個辦法:
NSString *url = @"https://test.niuniuhaoguanjia.com/3.0.0/?service=City.GetCityList"; NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:certFilePath]; NSSet *certSet = [NSSet setWithObject:certData]; AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet]; policy.alloWinvalidCertificates = YES; policy.validatesDomainName = NO; _manager = [AFHTTPSessionManager manager]; _manager.securityPolicy = policy; _manager.requestSerializer = [AFHTTPRequestSerializer serializer]; _manager.responseSerializer = [AFHTTPResponseSerializer serializer]; _manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/JavaScript",@"text/plain", nil]; //封閉緩存防止攪擾測試r _manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; [_manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) { NSLog(@"setSessionDidBecomeInvalidBlock"); }]; //客戶端懇求驗證 重寫 setSessionDidReceiveAuthenticationChallengeBlock 辦法 __weak typeof(self)weakSelf = self; [_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __autoreleasing NSURLCredential *credential =nil; if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if([weakSelf.manager.securityPolicy evaLuateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if(credential) { disposition =NSURLSessionAuthChallengeUseCredential; } else { disposition =NSURLSessionAuthChallengePerformDefaultHandling; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { // client authentication SecIdentityRef identity = NULL; SecTrustRef trust = NULL; NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client"ofType:@"p12"]; NSFileManager *fileManager =[NSFileManager defaultManager]; if(![fileManager fileExistsAtPath:p12]) { NSLog(@"client.p12:not exist"); } else { NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12]; if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) { SecCertificateRef certificate = NULL; SecIdentityCopyCertificate(identity, &certificate); const void*certs[] = {certificate}; CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL); credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; disposition =NSURLSessionAuthChallengeUseCredential; } } } *_credential = credential; return disposition; }];
4)發起懇求
//第三步和這一步代碼是放在一同的,請留意哦 [_manager GET:url parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil]; NSLog(@"JSON: %@", dic); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"Error: %@", error); NSData *data = [error.userInfo objectForKey:@"com.alamofire.serialization.response.error.data"]; NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",str); }];
另外還要加上一個辦法:
+(BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data { OSStatus securityError = errSecSuccess; //client certificate password NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"證書密碼" forKey:(__bridge id)kSecImportExportPassphrase]; CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items); if(securityError == 0) { CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0); const void*tempIdentity =NULL; tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity); *outIdentity = (SecIdentityRef)tempIdentity; const void*tempTrust =NULL; tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust); *outTrust = (SecTrustRef)tempTrust; } else { NSLog(@"Failedwith error code %d",(int)securityError); return NO; } return YES; }
沒錯,我們是要封裝一下,可是要怎樣封裝呢?博主嘗試了集中都失敗了,真是百思不得解,置信自動去封裝的開發者也會碰到封裝後懇求失敗的問題,也許你成功了,但是這裡需求留意一個在block內運用變量的問題,詳細的可以去看博主怎樣封裝的。
到這裡,新的AF懇求https就曾經完畢了,想看封裝的,Demo放在最後。
3.單向驗證
說到這個,不得不說一下網上的很多辦法,都把單向驗證當作雙向的,其實也是並不了解其原理,關於原理,請看這裡
代碼完成AF都是一樣的:
//AF加上這句和上面的辦法 _manager.securityPolicy = [self customSecurityPolicy]; /**** SSL Pinning ****/ - (AFSecurityPolicy*)customSecurityPolicy { NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:cerPath]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; [securityPolicy setAlloWinvalidCertificates:YES]; NSSet *set = [NSSet setWithObjects:certData, nil]; [securityPolicy setPinnedCertificates:@[certData]]; /**** SSL Pinning ****/ return securityPolicy; }
4.Demo下載福利
由於證書平安問題,Demo 裡的證書博主刪除了,請見諒,請大家放入自己的證書。
老的AF訪問httpsDemo
新的AF訪問httpsDemo
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支持本站。
【詳解iOS開發 - 用AFNetworking完成https單向驗證,雙向驗證】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!