UIWebView для просмотра самозаверяемых веб-сайтов (нет частного api, а не NSURLConnection) - возможно ли это?
Здесь возникает масса вопросов, которые задают этот вопрос: могу ли я получить UIWebView
для просмотра самоподписанного веб-сайта HTTPS?
И ответы всегда включают:
- Используйте приватный вызов api для
NSURLRequest
: allowsAnyHTTPSCertificateForHost
- Вместо этого используйте
NSURLConnection
и делегат canAuthenticateAgainstProtectionSpace
и т.д.
Для меня это не будет выполнено.
(1) - означает, что я не могу успешно отправить в магазин приложений.
(2) - использование NSURLConnection означает CSS, изображения и другие вещи, которые для загрузки с сервера после получения начальной HTML-страницы не загружаются.
Кто-нибудь знает, как использовать UIWebView для просмотра самоподписанной https-страницы, пожалуйста, что не включает в себя два метода выше?
Или - если использовать NSURLConnection
можно на самом деле использовать для отображения веб-страницы с CSS, изображениями и всем остальным - это было бы здорово!
Cheers,
Stretch.
Ответы
Ответ 1
Наконец, я понял!
Что вы можете сделать, так это:
Инициируйте свой запрос с помощью UIWebView
как обычно. Затем - в webView:shouldStartLoadWithRequest
- мы отвечаем NO и вместо этого запускаем NSURLConnection с тем же запросом.
Используя NSURLConnection
, вы можете общаться с самозаверяющим сервером, так как мы можем контролировать аутентификацию с помощью дополнительных методов делегата, которые недоступны для UIWebView
. Таким образом, используя connection:didReceiveAuthenticationChallenge
, мы можем пройти аутентификацию против самоподписанного сервера.
Затем в connection:didReceiveData
мы отменим запрос NSURLConnection
и снова запустим тот же запрос, используя UIWebView
-, который будет работать сейчас, потому что мы уже прошли аутентификацию сервера:)
Ниже приведены соответствующие фрагменты кода.
Примечание. Переменные экземпляра, которые вы увидите, имеют следующий тип:
UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request
(Я использую экземпляр var для _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];
}
Я надеюсь, что это поможет другим с той же проблемой, что и я!
Ответ 2
Надежный ответ кажется отличным обходным решением, но он использует устаревшие API. Итак, я подумал, что это может быть полезно для обновления кода.
В этом примере кода я добавил подпрограммы в ViewController, который содержит мой UIWebView. Я сделал 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, когда загружаю представление и не reset его. Это, по-видимому, позволяет UIWebView выполнять несколько запросов на одном сайте. Я не пытался переключать сайты и пытаться вернуться. Это может привести к необходимости сброса _Authenticated. Кроме того, если вы переключаете сайты, вы должны хранить словарь (одна запись для каждого хоста) для _Authenticated, а не BOOL.
Ответ 3
Это Панацея!
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.
}
Ответ 4
Если вы хотите получить доступ к закрытому серверу с самозаверяющим сертификатом только для тестирования, вам не нужно писать код. Вы можете вручную сделать общесистемный импорт сертификата.
Для этого вам необходимо загрузить сертификат сервера с помощью сафари для мобильных устройств, который затем запрашивает импорт.
Это можно использовать при следующих обстоятельствах:
- количество тестовых устройств невелико.
- вы доверяете сертификату сервера
Если у вас нет доступа к сертификату сервера, вы можете вернуться к следующему методу для извлечения его с любого 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.
Ответ 5
Это умное обходное решение. Тем не менее, возможно, лучшее (хотя и более интенсивно использующее код) решение было бы использовать NSURLProtocol, как показано в примере кода Apple CustomHTTPProtocol. Из README:
"CustomHTTPProtocol показывает, как использовать подкласс NSURLProtocol для перехвата связей NSURLC, созданных подсистемой высокого уровня, которая в противном случае не раскрывает его сетевые подключения. В этом конкретном случае он перехватывает HTTPS-запросы, сделанные веб-представлением, и переопределяет сервер доверия, позволяя вам просматривать сайт, чей сертификат по умолчанию не доверен".
Ознакомьтесь с полным примером:
https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html
Ответ 6
Это быстрый совместимый эквивалент, который работает для меня. Я не преобразовал этот код, чтобы использовать 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!)
}
Ответ 7
Здесь рабочий код 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)
}
}
Ответ 8
Чтобы построить @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 Loads
в Info.plist
. Меня смущает то, почему NSURLConnection
будет работать (по-видимому, одна и та же идея), но не NSURLSession
.