轉自: http://www.cocoachina.com/bbs/read.php?tid-55317-fpage-61.html
關於在UIwebView中訪問HTTPS網站的幾種方法 這兩天一直在研究如何用UIWebView訪問HTTPS網站,試過很多方法,但都有這樣那樣的缺陷,下面簡單分享一下,希望各位提點意見:
1。調用私人API
最簡單,也最危險的方法,調用 setAllowsAnyHTTPSCertificate:forHost ,後果怎麼樣就不用我說了吧。
2. libCurl
這是一個開源項目,用C語言寫的URL轉換庫,是基于于openssl的,可以支援目前大部分的URL,包括DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET 和 TFTP,而且用這個不會使用蘋果禁止的私人API,所以可以順利通過App Store的審核。 但是有一個問題,就是要自己編譯libcurl和openssl 的兩個靜態庫,其中的配置比較麻煩,不熟悉在unix環境下編譯靜態庫的朋友們可能就會皺眉頭了。這有兩個連結,可以參考 :
http://www.therareair.com/2009/01/01/tutorial-how-to-compile-openssl-for-the-iphone/
http://www.yifeiyang.net/iphone-web-development-skills-of-the-article-5-https-server-using-libcurl-to-connect/
編譯成功後,將libcurl引入工程,再加上下面這些代碼,就可以順利訪問HTTPS網站了
/*************** 調用libcurl 訪問HTTPS網站 ****************
const char *urlstring=[homeUrl cStringUsingEncoding:NSUTF8StringEncoding];
CURL *curl;
CURLcode res;
char* buffer;
int buflen=1024;
size_t* n;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://10.20.7.236/login.aspx?ReturnUrl=%2f");
//curl_easy_setopt(curl, CURLOPT_URL, urlstring);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
res = curl_easy_perform(curl);
if (0 != res) {
fprintf(stderr, "curl error: %d/n", res);
}
//res=curl_easy_recv(curl, buffer, buflen, n);
//printf("%s",buffer);
curl_easy_cleanup(curl);
}
*****************************************************/
通過聯機調試在我的ipod上成功的訪問到了HTTPS網站,在控制台中可以看到網頁的內容,剩下的就是如何把這些內容存到一個字元數組裡面,再通過調用UIwebView的loadHTMLString方法,就可以顯示內容了, 不過最後這一步我還沒有找到方法,希望有興趣的朋友可以幫我解決這個問題。
3。ASIHTTPRequest
這個開源項目也是流行很久了, 但是一般都是用來替代NSURLConnection,其實裡面的功能還是挺強大的,只是對於HTTPS訪問這一塊我有點不太滿意,
裡面有一個方法,是可以直接忽略認證驗證的,
disabling secure certificate validation
You may wish to use this for testing purposes if you have a self-signed secure certificate. I recommend purchasing a certificate from a trusted certificate authority and leaving certificate validation turned on for production applications.
[request setValidatesSecureCertificate:NO];
但是這樣做有一點危險,如果是放在企業環境裡部署的話,那外面的人都可以訪問內部的網站了,SSL這一層就形同虛設。所以我又找了一下,發現他還有另一個方法,可以設定用戶端的認證:
Client certificates support
If your server requires the use of client certificates, as of v1.8 it is now possible to send them with your request.
// Will send the certificate attached to the identity (identity is a SecIdentityRef)
[request setClientCertificateIdentity:identity];
// Add an additional certificate (where cert is a SecCertificateRef)
[request setClientCertificates:[NSArray arrayWithObject:(id)cert]];
There is a helper function in 'ClientCertificateTests.m' in the iPhone / iPad sample app that can create a SecIdentityRef from PKCS12 data (this function only works on iOS).
這個方法需要把p12檔案放進工程裡面,然後調用[ClientCertificateTest extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]; 來擷取identity
,這個方法是這樣的
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData *)inPKCS12Data
{
OSStatus securityError = errSecSuccess;
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"" forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((CFDataRef)inPKCS12Data,(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(@"Failed with error code %d",(int)securityError);
return NO;
}
return YES;
}
這個方法需要引用到Security.framework這個架構(我第一次沒有引用,結果報錯,我想官方的東西怎麼還有錯呢,鬱悶了好久才發現是這個架構沒有引用,暈死)。之後應該就是通過[ASIHTTPRequest startSynchronous] 可以訪問HTTPS網站了。 為什麼說是應該呢? 因為我也沒有試出來,呵呵 ```可能是認證的問題,我在extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data 這個方法裡擷取到的securityError 為-26275 ,在網上找了半天也沒找出個結果來,所以也只能暫時擱置了。
-(void) viewHomePage{
NSURL *url=[NSURL URLWithString:homeUrl];
NSBundle *bundle = [NSBundle mainBundle]; //取得mainBundle
NSString *plistPath = [bundle pathForResource:@"Root" ofType:@"plist"]; //取得檔案路徑
// 或可以寫成
// NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"檔案名稱" ofType:@"plist"];
//讀取到一個NSDictionary
NSArray *array=[[NSArray alloc]initWithContentsOfFile:plistPath];
NSDictionary *dictionary = [array objectAtIndex:1];
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults registerDefaults:dictionary];
BOOL needAuthentication=[defaults boolForKey:@"needAuth"];
SecIdentityRef identity = NULL;
SecTrustRef trust = NULL;
NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ioa.bingosoft.net" ofType:@"pfx"]];
[SenchaShellViewController extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data];
//****************** 調用 ASIHTTPRequest 訪問HTTPS網站 ****************
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setClientCertificateIdentity:identity];
//[request setValidatesSecureCertificate:NO];
//[request startSynchronous];
[request setValidatesSecureCertificate:needAuthentication];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
[self.viewer loadHTMLString:[request responseString] baseURL: [request url]];
}
else {
NSLog(@"Error: %@", error );
}
}
4。 官方API
最後一個是官方的API, 按理說這個應該放到最前面,因為是官方的嘛,但是實際上我一次都沒有試成功過,所以有點懷疑他的真實性,不過也許有人成功了,所以也拿上來討論一下。
簡單來說通過NSurlconnection的這兩個委託方法來忽略認證,關於Certificate Authentication是有官方文檔的,但是官方文檔太長了,我沒有心思看完,所以還沒有深入研究下去。
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
//if ([trustedHosts containsObject:challenge.protectionSpace.host])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]
forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}