簡體   English   中英

UIWebView可以查看自簽名網站(沒有私有api,沒有NSURLConnection)-可以嗎?

[英]UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?

有很多問題問這個問題:我可以獲取UIWebView來查看自簽名的HTTPS網站嗎?

答案總是涉及以下兩個方面:

  1. NSURLRequest使用私有api調用: allowsAnyHTTPSCertificateForHost
  2. 使用NSURLConnection代替,並且委托可以canAuthenticateAgainstProtectionSpace

對我來說,這些不會。
(1)-表示我無法成功提交到應用商店。
(2)-使用NSURLConnection意味着在加載初始HTML頁面后必須從服務器獲取的CSS,圖像和其他內容不會加載。

有誰知道如何使用UIWebView來查看自簽名的https網頁,而這不涉及上述兩種方法?

或者-如果實際上可以使用NSURLConnection來渲染包含CSS,圖像和其他所有內容的網頁,那就太好了!

干杯,
伸展。

最后我明白了!

您可以執行以下操作:

照常使用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];
}

我希望這可以幫助其他人解決我遇到的同樣的問題!

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

如果您想使用自簽名證書訪問專用服務器以進行測試,則無需編寫代碼。 您可以手動在系統范圍內導入證書。

為此,您需要使用移動瀏覽器下載服務器證書,然后提示輸入。

在以下情況下可以使用:

  • 測試設備數量少
  • 您信任服務器的證書

如果您無權訪問服務器證書,則可以使用以下方法從任何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。

這是一個聰明的解決方法。 但是,一個更好的解決方案(盡管需要更多代碼)將使用NSURLProtocol,如Apple的CustomHTTPProtocol示例代碼所示。 從自述文件:

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

打造關@ spirographer的答案 ,我把東西在一起的雨燕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發出的任何請求都沒有在會話中完成,這就是為什么連接被拒絕的原因。 我確實在Info.plist設置了Allow Arbitrary Loads 令我困惑的是為什么NSURLConnection可以工作(看似相同的想法),而不是NSURLSession

不推薦使用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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM