馬上就要元旦了,網上流傳元旦之后蘋果會對所有的app進行https的驗證,據說會拒絕所有沒有使用https的app。但是后來又聽說是我們開發者誤解了,元旦過后還是會支持http,不過開發者需要說明為什么不用https。不過躲得過初一躲不過十五,還是早點適配https好些啊。然后弄了幾天找了好多博客和文檔,才暫時解決了這個問題。所謂https,只不過是在http的基礎上增加了ssl層的驗證(這樣說不是很准確),也就是在原來的數據包的基礎上加密了一下,然而加密的工作不需要我們開發者來做,只需在對的位置做好證書的驗證就行了。其實我對ssl層理解也不深,想着以后有時間一定要把網絡這一塊掌握,不然下次碰到這種問題還是不好解決。廢話不多說,還是先看看代碼吧。
首先是對NSURLConnection的適配,不管是原生的還是AF的,歸根結底都是要用它去連接服務器。
1.如果使用AF進行網絡數據請求,那么使用如下方法即可:
1 - (AFSecurityPolicy*)customSecurityPolicy 2 3 { 4 //先導入證書 5 //在這加證書,一般情況適用於單項認證 6 NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ailian" ofType:@"cer"];//證書的路徑 7 8 NSData *certData = [NSData dataWithContentsOfFile:cerPath]; 9 if (certData==nil) { 10 return nil; 11 } 12 // AFSSLPinningModeCertificate 使用證書驗證模式 13 14 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; 15 16 // allowInvalidCertificates 是否允許無效證書(也就是自建的證書),默認為NO 17 18 // 如果是需要驗證自建證書,需要設置為YES 19 20 securityPolicy.allowInvalidCertificates = YES; 21 22 //validatesDomainName 是否需要驗證域名,默認為YES; 23 24 //假如證書的域名與你請求的域名不一致,需把該項設置為NO;如設成NO的話,即服務器使用其他可信任機構頒發的證書,也可以建立連接,這個非常危險,建議打開。 25 26 //置為NO,主要用於這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。 27 28 //如置為NO,建議自己添加對應域名的校驗邏輯。 29 30 securityPolicy.validatesDomainName = NO; 31 32 securityPolicy.pinnedCertificates = @[certData]; 33 34 return securityPolicy; 35 36 }
其實,在這里不需要使用服務器的證書,本人親測過。不過為了保險起見還是加上,還需要注意一點,AF3.0需要用到der格式的證書。
然后加上下面的代碼:
_manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:[self getHostURL]]];
[_manager setSecurityPolicy:[self customSecurityPolicy]];
2.如果是原生的NSURLConnection,那么需要在NSURLConnectionDelegate的一個方法里面加代碼,如下:
1 - (void)connection:(NSURLConnection *)connection 2 willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 3 { 4 NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]; 5 //導入證書 NSLog(@"thePath===========%@",thePath); 6 NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; 7 CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; 8 9 SecIdentityRef identity = NULL; 10 // extract the ideneity from the certificate 11 [self extractP12Data:inPKCS12Data toIdentity:&identity]; 12 13 SecCertificateRef certificate = NULL; 14 SecIdentityCopyCertificate (identity, &certificate); 15 16 const void *certs[] = {certificate}; 17 // CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); 18 // create a credential from the certificate and ideneity, then reply to the challenge with the credential 19 //NSLog(@"identity=========%@",identity); 20 NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:nil persistence:NSURLCredentialPersistencePermanent]; 21 22 // credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; 23 24 [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; 25 }
接下來是對NSURLSession的適配,現在公認更好的網絡請求類,支持后台的數據下載和上傳,而且自身是安全的。然而,在使用NSURLSession時需要在一個代理方法里配置ssl證書。如下:
1 - (void)URLSession:(NSURLSession *)session 2 didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 3 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 4 { 5 NSString *method = challenge.protectionSpace.authenticationMethod; 6 if([method isEqualToString:NSURLAuthenticationMethodServerTrust]){ 7 NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; 8 completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 9 return; 10 } 11 NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]; 12 NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; 13 CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(PKCS12Data); 14 SecIdentityRef identity; 15 // 讀取p12證書中的內容 16 OSStatus result = [self extractP12Data:inPKCS12Data toIdentity:&identity]; 17 if(result != errSecSuccess){ 18 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); 19 return; 20 } 21 SecCertificateRef certificate = NULL; 22 SecIdentityCopyCertificate (identity, &certificate); 23 const void *certs[] = {certificate}; 24 CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); 25 NSURLCredential *credential1 = [NSURLCredential credentialWithIdentity:identity certificates:(NSArray*)CFBridgingRelease(certArray) persistence:NSURLCredentialPersistencePermanent]; 26 completionHandler(NSURLSessionAuthChallengeUseCredential, credential1); 27 }
1 - (OSStatus) extractP12Data:(CFDataRef)inP12Data toIdentity:(SecIdentityRef*)identity { 2 OSStatus securityError = errSecSuccess; 3 CFStringRef password = CFSTR("clientepass"); 4 const void *keys[] = { kSecImportExportPassphrase }; 5 const void *values[] = { password }; 6 CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); 7 CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 8 securityError = SecPKCS12Import(inP12Data, options, &items); 9 if (securityError == 0) { 10 CFDictionaryRef ident = (CFDictionaryRef)CFArrayGetValueAtIndex(items,0); 11 const void *tempIdentity = NULL; 12 tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity); 13 *identity = (SecIdentityRef)tempIdentity; 14 } 15 if (options) { 16 CFRelease(options); 17 } 18 return securityError; 19 }
其實適配https沒有我們想象的那么復雜,你找對了地方可能幾分鍾就弄好了,找不到,可能花幾天。不管怎樣,最后適配成功還是要感謝網上的一些大神,雖然官網上面也有答案,但畢竟時間不等人,等我研究透徹,估計蘋果又會有新的東西出來吧。至此,希望能幫到正在為https適配而憂傷的小伙伴們。
