Как правильно выполнить аутентификацию в UIWebView? - PullRequest
19 голосов
/ 25 января 2012

Я бы хотел поддерживать базовую аутентификацию HTTP в моем UIWebView.

На данный момент я отменяю запросы в

webView:shouldStartLoadWithRequest:navigationType: затем обработайте их в моем собственном NSURLConnectionDelegate, чтобы проверить и предоставить учетные данные, если это необходимо. Затем я использую loadData:MIMEType:textEncodingName:baseURL: для представления HTML в веб-представлении. Это прекрасно работает для любых URL, которые передаются делегату.

Моя проблема в том, что делегат никогда не вызывается для встроенных элементов, таких как изображения, файлы JavaScript или CSS. Поэтому, если у меня есть страница HTML, которая ссылается на изображение, защищенное базовой аутентификацией, это изображение не может быть загружено должным образом. Кроме того, webView:didFinishLoad: никогда не вызывается, поскольку веб-представление не может полностью загрузить страницу.

Я проверил этот случай с Terra, сторонним браузером, доступным в App Store, и он может полностью справиться с этой ситуацией. Я думаю, что было бы возможно решить эту проблему, предоставив мой собственный NSURLProtocol, но это кажется слишком сложным. Чего мне не хватает?

Ответы [ 5 ]

25 голосов
/ 06 марта 2012

Попробуйте использовать sharedCredentialStorage для всех доменов, которые необходимо аутентифицировать.

Вот рабочий пример для UIWebView, который был протестирован на Windows IIS с включенной только базовой аутентификацией

Это как добавить учетные данные вашего сайта:

NSString* login = @"MYDOMAIN\\myname";
NSURLCredential *credential = [NSURLCredential credentialWithUser:login
                                                         password:@"mypassword"
                                                      persistence:NSURLCredentialPersistenceForSession];

NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
                                         initWithHost:@"myhost"
                                                 port:80
                                             protocol:@"http"
                                                realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge
                                 authenticationMethod:NSURLAuthenticationMethodDefault];

[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
                                                    forProtectionSpace:protectionSpace];
[protectionSpace release];

Редактировать: тот же код в Swift 4

let login = "MYDOMAIN\\myname"
let credential = URLCredential(user:login, password:"mypassword", persistence:.forSession)
let protectionSpace = URLProtectionSpace(host:"myhost", port:80, protocol:"http", realm:"myhost", authenticationMethod:NSURLAuthenticationMethodDefault)
URLCredentialStorage.shared.setDefaultCredential(credential, for:protectionSpace)

Предполагается, что ваш webView теперь работает, если он не работает, используйте следующий код для отладки, особенно проверьте сообщения журнала didReceiveAuthenticationChallenge.

    #import "TheSplitAppDelegate.h"
    #import "RootViewController.h"

    @implementation TheSplitAppDelegate

    @synthesize window = _window;
    @synthesize splitViewController = _splitViewController;
    @synthesize rootViewController = _rootViewController;
    @synthesize detailViewController = _detailViewController;

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // Override point for customization after application launch.
        // Add the split view controller's view to the window and display.
        self.window.rootViewController = self.splitViewController;
        [self.window makeKeyAndVisible];

        NSLog(@"CONNECTION: Add credentials");

        NSString* login = @"MYDOMAIN\\myname";
        NSURLCredential *credential = [NSURLCredential credentialWithUser:login
                                                                 password:@"mypassword"
                                                              persistence:NSURLCredentialPersistenceForSession];

        NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
                                                 initWithHost:@"myhost"
                                                 port:80
                                                 protocol:@"http"
                                                 realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge
                                                 authenticationMethod:NSURLAuthenticationMethodDefault];


        [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
        [protectionSpace release];    

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://myhost/index.html"]
                                                               cachePolicy:NSURLRequestReloadIgnoringCacheData
                                                           timeoutInterval:12
                                        ];

        NSLog(@"CONNECTION: Run request");
        [[NSURLConnection alloc] initWithRequest:request delegate:self];

        return YES;
    }

    - (void)applicationWillResignActive:(UIApplication *)application
    {

    }

    - (void)applicationDidEnterBackground:(UIApplication *)application
    {

    }

    - (void)applicationWillEnterForeground:(UIApplication *)application
    {

    }

    - (void)applicationDidBecomeActive:(UIApplication *)application
    {

    }

    - (void)applicationWillTerminate:(UIApplication *)application
    {

    }

    - (void)dealloc
    {
        [_window release];
        [_splitViewController release];
        [_rootViewController release];
        [_detailViewController release];
        [super dealloc];
    }

    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    {
        NSLog(@"CONNECTION: got auth challange");
        NSString* message = [NSString stringWithFormat:@"CONNECTION: cred cout = %i", [[[NSURLCredentialStorage sharedCredentialStorage] allCredentials] count]];
        NSLog(message);
        NSLog([connection description]);

        NSLog([NSString stringWithFormat:@"CONNECTION: host = %@", [[challenge protectionSpace] host]]);
        NSLog([NSString stringWithFormat:@"CONNECTION: port = %i", [[challenge protectionSpace] port]]);
        NSLog([NSString stringWithFormat:@"CONNECTION: protocol = %@", [[challenge protectionSpace] protocol]]);
        NSLog([NSString stringWithFormat:@"CONNECTION: realm = %@", [[challenge protectionSpace] realm]]);
        NSLog([NSString stringWithFormat:@"CONNECTION: authenticationMethod = %@", [[challenge protectionSpace] authenticationMethod]]);
    }

    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
        // release the connection, and the data object
        [connection release];

        // inform the user
        NSLog(@"CONNECTION: failed! Error - %@ %@",
              [error localizedDescription],
              [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
    } 

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
    {
        NSLog(@"CONNECTION: received response via nsurlconnection");
    }

    - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
    {
        NSLog(@"CONNECTION: USE!");
        return YES;
    }


    @end

Окончательное решение для аутентификации WebView было основано на реализации пользовательского протокола. Все протоколы зарегистрированы как стек, поэтому, если вы переопределите протокол HTTP, он будет перехватывать все запросы, поступающие из webView, поэтому вы должны проверить атрибуты, связанные с входящим запросом, перепаковать его в новый запрос и отправить его снова через ваше собственное соединение. Поскольку вы в стеке, ваш запрос немедленно возвращается к вам, и вы должны его игнорировать. Таким образом, он сводит стек протоколов к реальной реализации протокола HTTP, поскольку ваш запрос не аутентифицирован, вы получите запрос аутентификации. А после аутентификации вы получите реальный ответ от сервера, поэтому вы перепаковываете ответ и отвечаете на оригинальный запрос, полученный от webView, и все.

Не пытайтесь создавать новые тела запросов или ответов, вам нужно просто отправить их заново. Конечный код будет приблизительно 30-40 строк кода, и это довольно просто, но требует много отладки и проверки.

К сожалению, я не могу предоставить здесь код, поскольку я уже назначен другому проекту, я просто хотел сказать, что мое сообщение неверно, оно застревает, когда пользователь меняет пароль.

8 голосов
/ 01 апреля 2013

Секрет базовой аутентификации HTTP с использованием какао - знание NSURL и связанных классов.

  • NSURL
  • NSURLRequest / NSMutableURLRequest
  • NSURLConnection
  • NSURLCredential
  • NSURLCredentialStorage
  • NSURLProtectionSpace
  • UIWebView / WebView / NIWebController и т. Д.

Настоящая магия исходит от NSURLConnection. По словам devDocs, «объект NSURLConnection обеспечивает поддержку для загрузки URL-запроса». Если вы хотите загрузить какой-либо URL в фоновом режиме, не отображая его, вы должны использовать NSURLConnection. Реальная сила NSURLConnection заключается в методе

+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate

Протокол NSURLConnectionDelegate имеет методы для ответа на успешные соединения, фатальные ошибки и проблемы аутентификации. Если вы пытаетесь получить доступ к данным, защищенным базовой HTTP-аутентификацией, это то, как это делает Какао. На данном этапе пример должен внести некоторую ясность.

//basic HTTP authentication
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL:url
                              cachePolicy:NSURLRequestReloadIgnoringCacheData
                          timeoutInterval:12];
[self.webView openRequest:request];
(void)[NSURLConnection connectionWithRequest:request delegate:self];

Это создает URL. Из URL создается URLRequest. URLRequest затем загружается в веб-представление. Запрос также используется для создания URLConnection. На самом деле мы не используем соединение, но нам нужно получать уведомления об аутентификации, поэтому мы устанавливаем делегата. От делегата нам нужны только два метода.

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{  
    NSURLCredential * cred = [NSURLCredential credentialWithUser:@"username"
                                                    password:@"password"
                                                 persistence:NSURLCredentialPersistenceForSession];
[[NSURLCredentialStorage sharedCredentialStorage]setCredential:cred forProtectionSpace:[challenge protectionSpace]];

}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
    return YES;
}

При возникновении проблемы с аутентификацией учетные данные добавляются в хранилище учетных данных. Вы также указываете соединению использовать хранилище учетных данных.

4 голосов
/ 28 марта 2014

Я только что реализовал это, установив базовые учетные данные для аутентификации, используя NSMutableURLRequest для UIWebView.Это также позволяет избежать приема в оба конца при реализации sharedCredentialStorage (конечно, есть компромиссы).

Решение:

    NSString *url = @"http://www.my-url-which-requires-basic-auth.io"
    NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, password];
    NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
    NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]];
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
    [mutableRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
    NSURLRequest *request = [mutableRequest copy];
    NSURLRequest *request = [NSURLRequest basicAuthHTTPURLRequestForUrl:url];
    [self.webView loadRequest:request];

Вы можете получить категорию NSData + Base64, которая реализует base64EncodedString для NSData от Страница Мэтта Галлахера (она была внизу сообщения в блоге, когда я его скачивал)

2 голосов
/ 15 октября 2012

Для TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm] Для SVWebViewController [https://github.com/samvermette/SVWebViewController]

0 голосов
/ 09 августа 2013

Обязательно помните, что выход из системы не так прост с сеансами и учетными данными UIWebView. Смотрите ответ здесь: https://stackoverflow.com/a/18143902/2116338.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...