詳解iOS開發 - 用AFNetworking實現https單向驗證,雙向驗證_IOS

來源:互聯網
上載者:User

自蘋果宣布2017年1月1日開始強制使用https以來,htpps慢慢成為大家討論的對象之一,不是說此前https沒有出現,只是這一決策讓得開發人員始料未及,博主在15年的時候就做過https的介面,深知此坑之深,原因就是自身對這方面知識不瞭解加上網上的資料少,除此外還有部落格不知對錯就互相轉載,導致當時網上幾乎找不到能用的代碼,這一點,博主說的毫不誇張。

鑒於此,博主一直想填一下這個坑,多增加一些正確的代碼,來供廣大開發人員使用,後來一直被擱置,經過嘗試後,博主現將整理好的代碼發布在這裡,希望能幫到焦急尋找的開發人員。

1.先來說說老的AFNetworking2.x怎麼來實現的

博主在網上看過幾篇文章,其中說的一些方法是正確的,但是卻並不全對,由於那幾篇部落格幾乎一樣,博主不能確定最早的那篇是誰寫的,所以就重新在下面說明下方法:

1)倒入client.p12認證;

2)在plist檔案做如圖配置:

3)在AFNetworking中修改一個類:

找到這個檔案,在裡面增加一個方法:

- (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 *)connectionwillSendRequestForAuthenticationChallenge:(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);  }];

到此,老版的AFNetworking請求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

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.