ios UIWebView 查看自签名网站(没有私有 api,不是 NSURLConnection) - 有可能吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11573164/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?
提问by Stretch
There's a load of questions which ask this: Can I get UIWebView
to view a self signed HTTPS website?
有很多问题会问这个问题:我可以UIWebView
查看自签名的 HTTPS 网站吗?
And the answers always involve either:
答案总是涉及:
- Use the private api call for
NSURLRequest
:allowsAnyHTTPSCertificateForHost
- Use
NSURLConnection
instead and the delegatecanAuthenticateAgainstProtectionSpace
etc
- 使用私有 api 调用
NSURLRequest
:allowsAnyHTTPSCertificateForHost
- 使用
NSURLConnection
代替和委托canAuthenticateAgainstProtectionSpace
等
For me, these won't do.
(1) - means I can't submit to the app store successfully.
(2) - using NSURLConnection means the CSS, images and other things that have to be fetched from the server after receiving the initial HTML page do not load.
对我来说,这些不行。
(1) - 表示我无法成功提交到应用商店。
(2) - 使用 NSURLConnection 意味着在接收到初始 HTML 页面后必须从服务器获取的 CSS、图像和其他东西不会加载。
Does anyone know how to use UIWebView to view a self-signed https webpage please, which does not involve the two methods above?
请问有谁知道如何使用UIWebView查看自签名https网页,不涉及上述两种方法?
Or - If using NSURLConnection
can in fact be used to render a webpage complete with CSS, images and everything else - that would be great!
或者 - 如果 usingNSURLConnection
实际上可以用来渲染一个包含 CSS、图像和其他所有内容的网页 - 那会很棒!
Cheers,
Stretch.
干杯,
舒展。
回答by Stretch
Finally I got it!
最后我明白了!
What you can do is this:
你可以做的是:
Initiate your request using UIWebView
as normal. Then - in webView:shouldStartLoadWithRequest
- we reply NO, and instead start an NSURLConnection with the same request.
UIWebView
像往常一样启动您的请求。然后 - 在webView:shouldStartLoadWithRequest
- 我们回复NO,而是使用相同的请求启动 NSURLConnection 。
Using NSURLConnection
, you can communicate with a self-signed server, as we have the ability to control the authentication through the extra delegate methods which are not available to a UIWebView
. So using connection:didReceiveAuthenticationChallenge
we can authenticate against the self signed server.
使用NSURLConnection
,您可以与自签名服务器进行通信,因为我们有能力通过额外的委托方法来控制身份验证,而UIWebView
. 因此,使用connection:didReceiveAuthenticationChallenge
我们可以对自签名服务器进行身份验证。
Then, in connection:didReceiveData
, we cancel the NSURLConnection
request, and start the same request again using UIWebView
- which will work now, because we've already got through the server authentication :)
然后,在 中connection:didReceiveData
,我们取消NSURLConnection
请求,并再次使用UIWebView
-开始相同的请求- 现在可以使用了,因为我们已经通过了服务器身份验证:)
Here are the relevant code snippets below.
下面是相关的代码片段。
Note: Instance variables you will see are of the following type: UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request
注意:您将看到的实例变量属于以下类型: UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request
(I use an instance var for _request
as in my case it's a POST with lots of login details, but you could change to use the request passed in as arguments to the methods if you needed.)
(我使用一个实例_request
变量,因为在我的情况下它是一个带有大量登录详细信息的 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];
}
I hope this helps others with the same issue I was having!
我希望这可以帮助其他与我遇到相同问题的人!
回答by Prof Von Lemongargle
Stretch's answer appears to be a great workaround, but it uses deprecated APIs. So, I thought it might be worthy of an upgrade to the code.
Stretch 的答案似乎是一个很好的解决方法,但它使用了已弃用的 API。所以,我认为它可能值得升级代码。
For this code sample, I added the routines to the ViewController which contains my UIWebView. I made my UIViewController a UIWebViewDelegate and a NSURLConnectionDataDelegate. Then I added 2 data members: _Authenticated and _FailedRequest. With that, the code looks like this:
对于此代码示例,我将例程添加到包含我的 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];
}
I set _Authenticated to NO when I load the view and don't reset it. This seems to allow the UIWebView to make multiple requests to the same site. I did not try switching sites and trying to come back. That may cause the need for resetting _Authenticated. Also, if you are switching sites, you should keep a dictionary (one entry for each host) for _Authenticated instead of a BOOL.
我在加载视图时将 _Authenticated 设置为 NO 并且不重置它。这似乎允许 UIWebView 向同一站点发出多个请求。我没有尝试切换站点并尝试回来。这可能会导致需要重置 _Authenticated。此外,如果您要切换站点,您应该为 _Authenticated 而不是 BOOL 保留一个字典(每个主机一个条目)。
回答by Wilson Aguiar
This is the Panacea!
这就是灵丹妙药!
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.
}
回答by zliw
If you want to access a private server with a self-signed certificate just for testing you don't have to write code. You can manually do a system-wide import of the certificate.
如果您想访问带有自签名证书的私有服务器只是为了测试,您不必编写代码。您可以手动执行系统范围的证书导入。
To do this, you need to download the server certificate with mobile safari, which then prompts for an import.
为此,您需要使用 mobile safari 下载服务器证书,然后会提示导入。
This would be usable under the following circumstances:
这将在以下情况下可用:
- the number of test devices is small
- you're trusting the certificate of the server
- 测试设备数量少
- 您信任服务器的证书
If you don't have access to the server certificate, you can fallback to the following methodfor extracting it from any HTTPS-server (at least on Linux/Mac, windows guys will have to download an OpenSSL binary somewhere):
如果您无权访问服务器证书,则可以使用以下方法从任何 HTTPS 服务器中提取它(至少在 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
Note, that depending on the OpenSSL version, the certificate may be doubled in the file, so best have a look at it with a text editor. Put the file somewhere on the network or use the
请注意,根据 OpenSSL 版本,文件中的证书可能会翻倍,因此最好使用文本编辑器查看它。将文件放在网络上的某个位置或使用
python -m SimpleHTTPServer 8000
python -m SimpleHTTPServer 8000
shortcut to access it from your mobile safari at http://$your_device_ip:8000/server.pem.
从您的移动 Safari 访问它的快捷方式,网址为 http://$your_device_ip:8000/server.pem。
回答by Alex
This is a clever workaround. However, a possibly better (although more code intensive) solution would be to use an NSURLProtocol as demonstrated in Apple's CustomHTTPProtocol sample code. From the README:
这是一个聪明的解决方法。然而,一个可能更好(虽然代码更密集)的解决方案是使用 NSURLProtocol,如 Apple 的 CustomHTTPProtocol 示例代码中所示。从自述文件:
"CustomHTTPProtocol shows how to use an NSURLProtocol subclass to intercept the NSURLConnections made by a high-level subsystem that does not otherwise expose its network connections. In this specific case, it intercepts the HTTPS requests made by a web view and overrides server trust evaluation, allowing you to browse a site whose certificate is not trusted by default."
“CustomHTTPProtocol 展示了如何使用 NSURLProtocol 子类拦截由高级子系统创建的 NSURLConnections,该子系统不会以其他方式公开其网络连接。在这种特定情况下,它拦截 Web 视图发出的 HTTPS 请求并覆盖服务器信任评估,允许您浏览其证书在默认情况下不受信任的站点。”
Checkout the full example: https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html
查看完整示例:https: //developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html
回答by spirographer
This is a swift 2.0 compatible equivalent that works for me. I have not converted this code to use NSURLSession
instead of NSURLConnection
, and suspect that it would add a lot of complexity to get it right.
这是一个适用于我的 swift 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!)
}
回答by Velu Loganathan
Here the working code of swift 2.0
这里是 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)
}
}
回答by Tri Nguyen
To build off of @spirographer's answer, I put something together for a Swift 2.0 use case with NSURLSession
. However, this is still NOTworking. See more below.
为了建立@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!))
}
I will get back the initial HTML response, so the page renders the plain HTML, but there is no CSS styles applied to it (seems like the request to get CSS is denied). I see a bunch of these errors:
我将取回初始 HTML 响应,因此页面呈现纯 HTML,但没有应用任何 CSS 样式(似乎获取 CSS 的请求被拒绝)。我看到一堆这样的错误:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
It seems like any request made with webView.loadRequest
is done not within the session, which is why the connection is rejected. I do have Allow Arbitrary Loads
set in Info.plist
. What confuses me is why NSURLConnection
would work (seemingly the same idea), but not NSURLSession
.
这似乎是与提出的任何要求webView.loadRequest
不是会话内完成,这就是为什么拒绝连接。我确实Allow Arbitrary Loads
设置了Info.plist
. 让我感到困惑的是为什么NSURLConnection
会起作用(似乎是相同的想法),而不是NSURLSession
.
回答by Yatheesha B L
First thing UIWebView
is deprecated
第一件事UIWebView
已被弃用
use WKWebView
instead (available from iOS8)
使用WKWebView
替代(可从iOS8上)
set webView.navigationDelegate = self
放 webView.navigationDelegate = self
implement
实施
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let trust = challenge.protectionSpace.serverTrust!
let exceptions = SecTrustCopyExceptions(trust)
SecTrustSetExceptions(trust, exceptions)
completionHandler(.useCredential, URLCredential(trust: trust))
}
}
And add this in plist with domains you want to allow
并在 plist 中添加您要允许的域
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPSLoads</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>1.0</string>
<key>NSTemporaryExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>