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

问题描述 投票:49回答:9

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

答案总是涉及:

  1. 使用私人api调用NSURLRequestallowsAnyHTTPSCertificateForHost
  2. 使用NSURLConnection代替和代表canAuthenticateAgainstProtectionSpace

对我来说,这些都不行。 (1) - 表示我无法成功提交到应用商店。 (2) - 使用NSURLConnection意味着在加载初始HTML页面后必须从服务器获取的CSS,图像和其他内容。

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

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

干杯, 伸展。

iphone ios uiwebview
9个回答
76
投票

终于我明白了!

你能做的是:

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

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

然后,在connection:didReceiveData中,我们取消NSURLConnection请求,并使用UIWebView再次启动相同的请求 - 现在可以使用,因为我们已经通过服务器身份验证:)

以下是相关的代码段。

注意:您将看到的实例变量具有以下类型: UIWebView *_web NSURLConnection *_urlConnection NSURLRequest *_request

(我在_request中使用了一个实例var,因为在我的情况下,它是一个包含大量登录详细信息的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];
}

我希望这能帮助其他人解决我遇到的同样问题!


65
投票

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保留字典(每个主机一个条目)。


16
投票

这是灵丹妙药!


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.
}

7
投票

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

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

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

  • 测试设备的数量很少
  • 你信任服务器的证书

如果您无法访问服务器证书,则可以回退到following method以从任何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

请注意,根据OpenSSL版本,证书可能会在文件中加倍,因此最好使用文本编辑器查看。将文件放在网络上的某个位置或使用

python -m SimpleHTTPServer 8000

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


4
投票

这是一个聪明的解决方法。但是,可能更好(尽管代码更密集)的解决方案是使用NSURLProtocol,如Apple的CustomHTTPProtocol示例代码中所示。来自README:

“CustomHTTPProtocol显示了如何使用NSURLProtocol子类来拦截由高级子系统创建的NSURLConnections,该子系统不会暴露其网络连接。在这种特定情况下,它拦截Web视图发出的HTTPS请求并覆盖服务器信任评估,允许您浏览默认情况下证书不受信任的站点。“

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


3
投票

这是一个快速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!)
}

2
投票

这里是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)
                }
}

1
投票

为了建立@spirographer's answer,我把一些东西放在一起用于与NSURLSession的Swift 2.0用例。但是,这仍然无效。请参阅以下内容。

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 Loads设置了Info.plist。令我困惑的是为什么NSURLConnection会起作用(看似相同的想法),而不是NSURLSession


0
投票

第一件事UIWebView已被弃用

使用WKWebView代替(可从iOS8获得)

设置webView.navigationDelegate = self

实行

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))
    }

}

并在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>
© www.soinside.com 2019 - 2024. All rights reserved.