苦逼的我調試AFNetworking發送https請求的bug ---------調試了一個上午,終於解決了
認證信任的過程:
認證的信任是通過代理的方式進行信任(NSURLConnection和NSURLSession兩種方式進行信任) 用戶端信任認證的過程: 1.當用戶端要訪問伺服器的時候,伺服器向用戶端發送受保護的信任認證 2.用戶端判斷是否對用戶端發送的認證進行信任,, 3.如果信任.則用戶端會安裝公開金鑰在用戶端,而伺服器就擁有受保護認證的密鑰,每一次向伺服器請求資料的時候,伺服器會先將要發送的資料進行祕密金鑰加密,用戶端對所傳資料通過公開金鑰解密
下面分享一下自己的經驗:
一開始請求資料先要信任認證,然後才能請求資料,不過對於百度,apple官網這樣的大網站就不需要信任認證了,
只需要信任一次伺服器憑證
在信任認證的時候有兩種方式:
大家熟知的有NSURLSession和NSURLConnection兩種信任認證的方式,我會重點將第三種:
如果不信任認證的話會報下面的錯誤:
// NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9843)
// 原因:沒有信任認證
一.NSURLSession的方式信任認證是通過代理的方式:
1.先懶載入全域的會話:
@property (nonatomic, strong) NSURLSession *session;- (NSURLSession *)session { if (_session == nil) { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; } return _session;}
2.發起資料任務
// url NSURL *url = [NSURL URLWithString:@"https://網域名稱"]; // 發起資料任務 [[self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%@---%@",response,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); }] resume];
3.代理方法中實現認證的信任-----------
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler { /* <NSURLProtectionSpace: 0x7fef2b686e20>: Host:mail.itcast.cn, Server:https, Auth-Scheme:NSURLAuthenticationMethodServerTrust, */ // 判斷是否是信任伺服器憑證 if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { // 告訴伺服器,用戶端信任認證 // 建立憑據對象 NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; // 通過completionHandler告訴伺服器信任認證 completionHandler(NSURLSessionAuthChallengeUseCredential,credntial); } NSLog(@"protectionSpace = %@",challenge.protectionSpace);}
二.NSURLConnection的方式信任認證也是通過代理的方式: 1.發送請求:
// url NSURL *url = [NSURL URLWithString:@"https://mail.itcast.cn"]; // request NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 發送請求 [NSURLConnection connectionWithRequest:request delegate:self];
2.信任認證
#pragma mark - NSURLConnectionDataDelegate 代理方法- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { // 判斷是否是信任伺服器憑證 if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { // 告訴伺服器,用戶端信任認證 // 建立憑據對象 NSURLCredential *credntial = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; // 告訴伺服器信任認證 [challenge.sender useCredential:credntial forAuthenticationChallenge:challenge]; }}
3.擷取請求到的資料
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSLog(@"data = %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);}
三.AFNetworking的方式信任伺服器憑證: 先上代碼:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.securityPolicy.validatesDomainName = NO; manager.responseSerializer = [AFHTTPResponseSerializer serializer];// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html", nil]; [manager GET:@"https://網域名稱" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { NSLog(@"%@",downloadProgress); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"%@---%@",[responseObject class],responseObject); NSLog(@"%@",[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"%@",error); }];
注意解釋遇到的坑: 1.https的協議直接請求的時候會報下面的錯:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9843)
2.設定屬性 不能將 validatesDomainName設定為NO,當設定為NO以後可以發出任意的請求,所以要將其設定為YES(預設是YES)-----重點
manager.securityPolicy.validatesDomainName =YES;
AFNetworking 的安全相關設定放在 AFSecurityPolicy,它定義了三種 SSL Pinning Mode:
AFSSLPinningModeNone : 你不必將憑證跟你的 APP 一起打包,完全信任伺服器的憑證 AFSSLPinningModeCertificate : 比對伺服器憑證跟你的憑證是否完全符合 AFSSLPinningModePublicKey : 只比對伺服器憑證的 public key 跟你的憑證的 public key 是否匹配
那要選用何種模式比較好呢。
AFSSLPinningModeCertificate 比較安全但也比較麻煩,它會比對你打包的憑證跟伺服器的憑證是否一致。因為你的憑證是跟 APP 一起打包的,這也就代表說如果你的憑證過期了或是變動了,你就得出一版新的 APP 而且舊版 APP 的憑證就失效了。你也可以在每次 APP 啟動時,就自動連到某個伺服器下載最新的憑證,不過此時這個下載連線就會是有風險的。
AFSSLPinningModePublicKey 則是只有比對憑證裡的 public key,所以即使伺服器憑證有所變動,只要 public key 不變,就能通過驗證。
所以如果你能確保每個使用者總是使用最新版本的 APP(例如是公司企業內部專用的),那就可以考慮 AFSSLPinningModeCertificate,否則的話選擇 AFSSLPinningModePublicKey 是比較實際的作法。
解決了認證信任的問題,也就是說設定上面的屬性以後就可以信任認證了
但是問題沒有解決:越到了經常遇到的問題,就是請求的資料列印有問題,
Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptable content-type: text/html"
原因:
這是因為 AFNetworking預設把響應結果當成json來處理,(預設manager.responseSerializer = [AFJSONResponseSerializer serializer]) ,很顯然,我們請求的百度首頁 返回的並不是一個json文本,而是一個html網頁,但是AFNetworking並不知道,它堅信請求的結果就是一個json文本!然後固執地以json的形式去解析,顯然沒辦法把一個網頁解析成一個字典或者數組,所以產生了上述錯誤.
然而,我們期望它能夠正確地處理這個情形,而不是提示一個錯誤.
這時候 你必須告訴AFNetworking:別把這個網頁當json來處理!
解決辦法:
只需要在發送請求前加入:manager.responseSerializer = [AFHTTPResponseSerializer serializer]
以上步驟就可以實現https認證的信任和正確的擷取例如baidu.com首頁的html的源碼...
附加說明:
網頁上載入的資料本質是字串,我們將擷取到的網頁資料通過二進位轉字串的形式可以講網頁資料進行列印:
NSLog(@"%@",[[NSStringalloc]initWithData:responseObjectencoding:NSUTF8StringEncoding]);
有了上面的解決辦法是不是不知道如何擷取憑證能,別急,下面添加如何擷取制定網域名稱的憑證:
參考連結:
http://nelson.logdown.com/posts/2015/04/29/how-to-properly-setup-afnetworking-security-connection/
取得安全憑證 1. 確認有使用安全連線
如果你跟遠端伺服器是透過 HTTP 連線,那就不是安全連線,如果是 HTTPS 那就是安全連線。 2. 準備好網站的安全憑證
接下來我們需要憑證檔(Certification file),它的副檔名是 .cer,你可以跟你們的網站管理員詢問,通常他們都知道怎麼拿到這個檔案。
如果你的網站管理員沒有 .cer 檔,只有 .crt 檔,那你可以透過以下這行指令轉檔,要注意的是它是採用 DER 編碼格式(請自行將 myWebsite 替換成你想要的名字):
openssl x509 -in myWebsite.crt -out myWebsite.cer -outform der
如果很不幸的,你的網站管理員連 .crt 檔都沒有,那你也可以使用下列這一整行指令從你們的網站取得憑證(請自行將 www.mywebsite.com 替換成你們的網址):
openssl s_client -connect www.mywebsite.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > myWebsite.cer
現在你有一個憑證檔了。 3. 將憑證加入你的專案
將你的憑證拖拉放到 Xcode 專案底下,記得要把 Copy items if needed 跟 Add to targets 打勾