參考文章鏈接:
https://www.cnblogs.com/v-jing/p/6008964.html
http://www.cocoachina.com/ios/20151021/13722.html
https://www.cnblogs.com/weak/p/6142508.html
http://blog.csdn.net/samuelandkevin/article/details/53392748
http://www.jianshu.com/p/7c89b8c5482a
比較通用的解決辦法之一:
MKNetworkOperation 可以設置
request.shouldContinueWithInvalidCertificate = YES;
//采用NSURLConnection進行網絡請求,會調用此方法
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
//證書處理
if([kBaseURL rangeOfString:@"youhost"].location != NSNotFound)
{
}
// 判斷是否是信任服務器證書
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 告訴服務器,客戶端信任證書
// 創建憑據對象
NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 告訴服務器信任證書
[challenge.sender useCredential:credntial forAuthenticationChallenge:challenge];
}
}
在AFN中如何繞過證書驗證呢?設置AFSecurityPolicy參數:
//證書處理
//后台自建證書,因為證書無效導致AFN請求被取消,此段代碼用在外網測試環境
if([kBaseURL rangeOfString:@"testapp.gtax.cn"].location != NSNotFound){
AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
self.requestManager.securityPolicy = securityPolicy;
}
設置自定義證書的方法
引用方便學習,內容來源:http://www.jianshu.com/p/6b9c8bd5005a
發送HTTPS請求信任SSL證書和自簽名證書,分為三種情況
1.如果你的app服務端安裝的是SLL頒發的CA,可以使用系統方法直接實現信任SSL證書,關於Apple對SSL證書的要求請參考:蘋果官方文檔CertKeyTrustProgGuide
這種方式不需要在Bundle中引入CA文件,可以交給系統去判斷服務器端的證書是不是SSL證書,驗證過程也不需要我們去具體實現。
示例代碼:
NSURL *URL = [NSURL URLWithString:URLString]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10]; //創建同步連接 NSError *error = nil; NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; NSString *receivedInfo = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
當然,如果你需要同時信任SSL證書和自簽名證書的話還是需要在代碼中實現CA的驗證,這種情況在后面會提到。
2.基於AFNetWorking的SSL特定服務器證書信任處理,重寫AFNetWorking的customSecurityPolicy方法,這里我創建了一個HttpRequest類,分別對GET和POST方法進行了封裝,以GET方法為例:
+ (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure
{ // 1.獲得請求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; // 2.申明返回的結果是text/html類型 mgr.responseSerializer = [AFHTTPResponseSerializer serializer]; // 3.設置超時時間為10s mgr.requestSerializer.timeoutInterval = 10; // 加上這行代碼,https ssl 驗證。 if(openHttpsSSL) { [mgr setSecurityPolicy:[self customSecurityPolicy]]; } // 4.發送GET請求 [mgr GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObj){ if (success) { success(responseObj); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (error) { failure(error); } }]; }
+ (AFSecurityPolicy*)customSecurityPolicy
{
// /先導入證書 NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//證書的路徑 NSData *certData = [NSData dataWithContentsOfFile:cerPath]; // AFSSLPinningModeCertificate 使用證書驗證模式 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; // allowInvalidCertificates 是否允許無效證書(也就是自建的證書),默認為NO // 如果是需要驗證自建證書,需要設置為YES securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否需要驗證域名,默認為YES; //假如證書的域名與你請求的域名不一致,需把該項設置為NO;如設成NO的話,即服務器使用其他可信任機構頒發的證書,也可以建立連接,這個非常危險,建議打開。 //置為NO,主要用於這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。 //如置為NO,建議自己添加對應域名的校驗邏輯。 securityPolicy.validatesDomainName = NO; securityPolicy.pinnedCertificates = @[certData]; return securityPolicy; }
其中的cerPath就是app bundle中證書路徑,certificate為證書名稱的宏,僅支持cer格式,securityPolicy的相關配置尤為重要,請仔細閱讀customSecurityPolicy方法並根據實際情況設置其屬性。
這樣,就能夠在AFNetWorking的基礎上使用HTTPS協議訪問特定服務器,但是不能信任根證書的CA文件,因此這種方式存在風險,讀取pinnedCertificates中的證書數組的時候有可能失敗,如果證書不符合,certData就會為nil。
3.更改系統方法,發送異步NSURLConnection請求。
- (void)getDataWithURLRequest { //connection NSString *urlStr = @"https://developer.apple.com/cn/"; NSURL *url = [NSURL URLWithString:urlStr]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self]; [connection start]; }
重點在於處理NSURLConnection的didReceiveAuthenticationChallenge代理方法,對CA文件進行驗證,並建立信任連接。
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { /* //直接驗證服務器是否被認證(serverTrust),這種方式直接忽略證書驗證,直接建立連接,但不能過濾其它URL連接,可以理解為一種折衷的處理方式,實際上並不安全,因此不推薦。 SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust] forAuthenticationChallenge: challenge]; */ if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) { do { SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; NSCAssert(serverTrust != nil, @"serverTrust is nil"); if(nil == serverTrust) break; /* failed */ /** * 導入多張CA證書(Certification Authority,支持SSL證書以及自簽名的CA) */ NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cloudwin" ofType:@"cer"];//自簽名證書 NSData* caCert = [NSData dataWithContentsOfFile:cerPath]; NSString *cerPath2 = [[NSBundle mainBundle] pathForResource:@"apple" ofType:@"cer"];//SSL證書 NSData * caCert2 = [NSData dataWithContentsOfFile:cerPath2]; NSCAssert(caCert != nil, @"caCert is nil"); if(nil == caCert) break; /* failed */ NSCAssert(caCert2 != nil, @"caCert2 is nil"); if (nil == caCert2) { break; } SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert); NSCAssert(caRef != nil, @"caRef is nil"); if(nil == caRef) break; /* failed */ SecCertificateRef caRef2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert2); NSCAssert(caRef2 != nil, @"caRef2 is nil"); if(nil == caRef2) break; /* failed */ NSArray *caArray = @[(__bridge id)(caRef),(__bridge id)(caRef2)]; NSCAssert(caArray != nil, @"caArray is nil"); if(nil == caArray) break; /* failed */ OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray); NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed"); if(!(errSecSuccess == status)) break; /* failed */ SecTrustResultType result = -1; status = SecTrustEvaluate(serverTrust, &result); if(!(errSecSuccess == status)) break; /* failed */ NSLog(@"stutas:%d",(int)status); NSLog(@"Result: %d", result); BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed); if (allowConnect) { NSLog(@"success"); }else { NSLog(@"error"); } /* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */ /* https://developer.apple.com/library/mac/qa/qa1360/_index.html */ /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */ if(! allowConnect) { break; /* failed */ } #if 0 /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */ /* since the user will likely tap-through to see the dancing bunnies */ if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError) break; /* failed to trust cert (good in this case) */ #endif // The only good exit point return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust] forAuthenticationChallenge: challenge]; } while(0); } // Bad dog return [[challenge sender] cancelAuthenticationChallenge: challenge]; }
這里的關鍵在於result參數的值,根據官方文檔的說明,判斷(result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed)的值,若為1,則該網站的CA被app信任成功,可以建立數據連接,這意味着所有由該CA簽發的各個服務器證書都被信任,而訪問其它沒有被信任的任何網站都會連接失敗。該CA文件既可以是SLL也可以是自簽名。
NSURLConnection的其它代理方法實現
#pragma mark -- connect的異步代理方法 -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSLog(@"請求被響應"); _mData = [[NSMutableData alloc]init]; } -(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data { NSLog(@"開始返回數據片段"); [_mData appendData:data]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"鏈接完成"); //可以在此解析數據 NSString *receiveInfo = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:nil]; NSLog(@"received data:\\\\n%@",self.mData); NSLog(@"received info:\\\\n%@",receiveInfo); } //鏈接出錯 -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{ NSLog(@"error - %@",error); }
至此,HTTPS信任證書的問題得以解決,這不僅是為了響應Apple強制性使用ATS的要求,也是為了實際生產環境安全性的考慮,HTTPS是未來的趨勢,建議盡早支持。
如需參考Demo請移步在Github上的開源項目