UIWebView查看自签名的网站(没有私人API,而不是NSURLConnection) – 这是可能的吗?

有很多问题需要解答:我可以让UIWebView查看自签名的HTTPS网站吗?

答案总是涉及到:

  1. 使用私有api调用NSURLRequestallowsAnyHTTPSCertificateForHost
  2. 改为使用NSURLConnection ,委托可以通过canAuthenticateAgainstProtectionSpace

对我来说,这些都不行。
(1) – 意味着我无法成功提交到app store。
(2) – 使用NSURLConnection意味着CSS,图像和其他东西,必须从服务器获取后,收到初始HTML页面不加载。

有谁知道如何使用UIWebView来查看一个自签名的https网页,这不涉及以上两种方法?

或者 – 如果使用NSURLConnection实际上可以用来渲染一个完整的CSS,图像和其他一切的网页 – 这将是伟大的!

干杯,
伸展。

最后我明白了!

你可以做的是这样的:

正常使用UIWebView启动您的请求。 然后 – 在webView:shouldStartLoadWithRequest – 我们回复NO ,而是用相同的请求启动一个NSURLConnection。

使用NSURLConnection ,您可以与自签名服务器进行通信,因为我们可以通过UIWebView无法使用的额外委托方法来控制身份validation。 所以使用connection:didReceiveAuthenticationChallenge我们可以对自签名的服务器进行身份validation。

然后,在connection:didReceiveData ,我们取消NSURLConnection请求,并使用UIWebView再次启动相同的请求 – 现在将工作,因为我们已经通过服务器validation:)

以下是相关的代码片段。

注意:您将看到的实例variables具有以下types:
UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request

(我使用_request的实例var,就像我的情况那样,它是一个有很多login细节的POST,但是如果需要的话,你可以改变使用传入的方法作为参数的请求。

 #pragma mark - Webview delegate // Note: This method is particularly important. As the server is using a self signed certificate, // we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the // request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods // which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete // the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; { NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated); if (!_authenticated) { _authenticated = NO; _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self]; [_urlConnection start]; return NO; } return YES; } #pragma mark - NURLConnection delegate - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; { NSLog(@"WebController Got auth challange via NSURLConnection"); if ([challenge previousFailureCount] == 0) { _authenticated = YES; NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; { NSLog(@"WebController received response via NSURLConnection"); // remake a webview call now that authentication has passed ok. _authenticated = YES; [_web loadRequest:_request]; // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!) [_urlConnection cancel]; } // We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed. - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } 

我希望这可以帮助别人解决我遇到的同样的问题!

Stretch的答案似乎是一个很好的解决方法,但它使用了不推荐使用的API。 所以,我认为这可能值得升级到代码。

对于这个代码示例,我将例程添加到包含我的UIWebView的ViewController。 我让我的UIViewController一个UIWebViewDelegate和一个NSURLConnectionDataDelegate。 然后我添加了2个数据成员:_Authenticated和_FailedRequest。 这样,代码看起来像这样:

 -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { BOOL result = _Authenticated; if (!_Authenticated) { _FailedRequest = request; [[NSURLConnection alloc] initWithRequest:request delegate:self]; } return result; } -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURL* baseURL = [_FailedRequest URL]; if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) { NSLog(@"trusting connection to host %@", challenge.protectionSpace.host); [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; } else NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host); } [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse { _Authenticated = YES; [connection cancel]; [_WebView loadRequest:_FailedRequest]; } 

当我加载视图时,我将_Authenticated设置为NO,并且不重置它。 这似乎允许UIWebView向同一个站点发出多个请求。 我没有尝试切换网站,并试图回来。 这可能会导致需要重置_Authenticated。 另外,如果你正在切换网站,你应该保留一个字典(每个主机一个条目)为_Authenticated而不是BOOL。

这是万能的!


 BOOL _Authenticated; NSURLRequest *_FailedRequest; #pragma UIWebViewDelegate -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { BOOL result = _Authenticated; if (!_Authenticated) { _FailedRequest = request; NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; [urlConnection start]; } return result; } #pragma NSURLConnectionDelegate -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURL* baseURL = [NSURL URLWithString:@"your url"]; if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) { NSLog(@"trusting connection to host %@", challenge.protectionSpace.host); [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; } else NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host); } [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse { _Authenticated = YES; [connection cancel]; [self.webView loadRequest:_FailedRequest]; } - (void)viewDidLoad{ [super viewDidLoad]; NSURL *url = [NSURL URLWithString:@"your url"]; NSURLRequest *requestURL = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:requestURL]; // Do any additional setup after loading the view. } 

如果您想访问带有自签名证书的私人服务器以进行testing,则不必编写代码。 您可以手动执行系统范围的证书导入。

为此,您需要使用移动Safari浏览器下载服务器证书,然后提示导入。

这在以下情况下可以使用:

  • testing设备的数量很less
  • 你信任服务器的证书

如果您无权访问服务器证书,则可以使用以下方法从任何HTTPS服务器解压缩(至less在Linux / Mac上,Windows用户必须在某处下载OpenSSL二进制文件):

 echo "" | openssl s_client -connect $server:$port -prexit 2>/dev/null | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' >server.pem 

请注意,根据OpenSSL版本的不同,证书可能会在文件中加倍,所以最好使用文本编辑器来查看。 把文件放在networking上的某个地方或者使用

python -m SimpleHTTPServer 8000

通过http:// $ your_device_ip:8000 / server.pem从移动Safari浏览器访问它的快捷方式。

这是一个聪明的解决方法。 但是,如Apple的CustomHTTPProtocol示例代码所示,使用NSURLProtocol可能更好(但代码更密集)的解决scheme。 从自述文件:

“CustomHTTPProtocol显示了如何使用NSURLProtocol子类来截取高级子系统所做的NSURLConnections,在这种情况下,它拦截由Web视图所做的HTTPS请求并重写服务器信任评估,允许您浏览默认情况下证书不受信任的站点。“

查看完整示例: https : //developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html

这是一个快速2.0兼容的相当于我的作品。 我没有转换这个代码,而是使用NSURLSession而不是NSURLConnection ,并且怀疑它会增加很多复杂性以使其正确。

 var authRequest : NSURLRequest? = nil var authenticated = false var trustedDomains = [:] // set up as necessary func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { if !authenticated { authRequest = request let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)! urlConnection.start() return false } else if isWebContent(request.URL!) { // write your method for this return true } return processData(request) // write your method for this } func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { let challengeHost = challenge.protectionSpace.host if let _ = trustedDomains[challengeHost] { challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge) } } challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge) } func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) { authenticated = true connection.cancel() webview!.loadRequest(authRequest!) } 

这里是swift 2.0的工作代码

 var authRequest : NSURLRequest? = nil var authenticated = false func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { if !authenticated { authRequest = request let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)! urlConnection.start() return false } return true } func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) { authenticated = true connection.cancel() webView!.loadRequest(authRequest!) } func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { let host = "www.example.com" if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && challenge.protectionSpace.host == host { let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!) challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge) } else { challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge) } } 

为了构build@ spirographer的答案 ,我把一些Swift 2.0用例与NSURLSession放在一起。 但是,这仍然不起作用。 请参阅下文。

 func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { let result = _Authenticated if !result { let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue()) let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in if error == nil { if (!self._Authenticated) { self._Authenticated = true; let pageData = NSString(data: data!, encoding: NSUTF8StringEncoding) self.webView.loadHTMLString(pageData as! String, baseURL: request.URL!) } else { self.webView.loadRequest(request) } } } task.resume() return false } return result } func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)) } 

我将返回最初的HTML响应,所以页面呈现纯HTML,但没有应用CSS样式(看起来像获取CSS的请求被拒绝)。 我看到一堆这样的错误:

 NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 

似乎任何使用webView.loadRequest请求都不在会话中,这就是连接被拒绝的原因。 我确实Allow Arbitrary LoadsInfo.plist设置Allow Arbitrary Loads 。 令我困惑的是为什么NSURLConnection会工作(看起来是一样的想法),而不是NSURLSession