iOS: HTTP Basic/ дайджест-аутентификация с UIWebView
обзор
Я работаю над решением для входа в SAML (единый вход, похожий на openID) для приложения iOS, которое включает показ контроллера представления с UIWebView
и я сталкиваюсь с проблемой синхронизации и / или тайм-аута при обработке HTTP базовой / дайджест-аутентификации в UIWebView
,
В частности, когда клиент получает запрос аутентификации HTTP, я выскакиваю UIAlertView
запрашивая у пользователя идентификатор пользователя и пароль. Если пользователь может быстро ввести информацию (< 10 секунд), она работает. Однако, если запись занимает более 10 секунд, соединение, по-видимому, было разорвано и ничего не происходит.
Вопросы
- Есть ли тайм-аут на звонки
connection:didReceiveAuthenticationChallenge:
что помешало бы мне запрашивать у пользователя идентификатор пользователя и пароль (и ждать ввода пользователя)? У кого-нибудь есть обходной путь (например, какой-нибудь способ продлить время ожидания соединения)? - Есть ли лучший способ обрабатывать основные HTTP / аутентификации от дайджеста
UIWebView
чем подклассNSURLProtocol
?
Детали и код
Для большинства систем SAML, которые мы должны обработать, логин будет отображаться как обычная веб-страница в UIWebView
, Однако некоторые системы, с которыми нам нужно работать, прибегают к использованию базовой HTTP-аутентификации или дайджест-аутентификации HTTP для мобильных браузеров, поэтому мы должны быть в состоянии справиться и с этим.
Большие проблемы начинаются с того, что UIWebView не отображает сетевые вызовы внизу. Чтобы получить то, что мне нужно, я создал подкласс NSURLProtocol
и зарегистрировал его, если необходимо:
[NSURLProtocol registerClass:[SMURLProtocol class]];
С этим, этот метод на SMURLProtocol
вызывается при вызове HTTP basic/auth, поэтому я возвращаю YES, мы можем обработать HTTP basic & digest аутентификацию:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]
|| [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]);
}
Теперь я сказал сетевому стеку, что SMURLProtocol может обработать запрос аутентификации, поэтому он вызывает
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
NSString *authenticationMethod = [protectionSpace authenticationMethod];
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]
|| [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]) {
// Stash the challenge in an IVAR so we can use it later
_challenge = challenge;
// These network operations are often on a background thread, so we have to make sure to be on the foreground thread
// to interact with the UI. We tried the UIAlertView performSelectorOnMainThread, but ran into issues, so then
// we switched to GCD with a semaphore?
_dsema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
// Prompt the user to enter the userID and password
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"AUTHENTICATION_REQUIRED", @"")
message:[protectionSpace host]
delegate:self
cancelButtonTitle:NSLocalizedString(@"CANCEL", @"")
otherButtonTitles:NSLocalizedString(@"LOG_IN", @""), nil];
[alert setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
[alert show];
});
dispatch_semaphore_wait(_dsema, DISPATCH_TIME_FOREVER);
// --> when you get here, the user has responded to the UIAlertView <--
dispatch_release(_dsema);
}
}
Как вы можете видеть, я запускаю UIAlertView, чтобы запросить у пользователя идентификатор пользователя и пароль. Я должен сделать это обратно в главном потоке, потому что (очевидно, я не знаю наверняка) сетевой код работает в фоновом потоке. Я добавил семафор и явный код Grand Central Dispatch, чтобы обойти случайные сбои, которые я видел (основываясь на этой теме).
Последним элементом является делегат UIAlertView, который принимает идентификатор пользователя и пароль, который создает учетные данные для вызова:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (([alertView alertViewStyle] == UIAlertViewStyleLoginAndPasswordInput) && (buttonIndex == 1)) {
NSString *userID = [[alertView textFieldAtIndex:0] text];
NSString *password = [[alertView textFieldAtIndex:1] text];
// when you get the reply that should unblock the background thread, unblock the other thread:
dispatch_semaphore_signal(_dsema);
// Use the userID and password entered by the user to proceed
// with the authentication challenge.
[_challenge.sender useCredential:[NSURLCredential credentialWithUser:userID
password:password
persistence:NSURLCredentialPersistenceNone]
forAuthenticationChallenge:_challenge];
[_challenge.sender continueWithoutCredentialForAuthenticationChallenge:_challenge];
_challenge = nil;
}
}
Как я уже говорил в обзоре, все это прекрасно работает, если пользователь может ввести идентификатор пользователя и пароль менее чем за 10 секунд. Если это занимает больше времени, кажется, что время соединения истекло, и передача учетных данных отправителю запроса не имеет никакого эффекта.