概要
ビュー コントローラーを表示する iOS アプリ用の SAML ログイン (openID に似たシングル サインオン) ソリューションに取り組んでおり、UIWebView
HTTP 基本/ダイジェストを処理するときにタイミングやタイムアウトの問題が発生しています。の認証UIWebView
。
具体的には、クライアントが HTTP 認証チャレンジを取得するとUIAlertView
、ユーザーにユーザー ID とパスワードを求めるプロンプトを表示します。ユーザーがすばやく (10 秒未満で) 情報を入力できる場合、それは機能します。ただし、エントリに 10 秒以上かかると、接続が終了したように見え、何も起こりません。
質問
connection:didReceiveAuthenticationChallenge:
ユーザーにユーザー ID とパスワードの入力を求める (そしてユーザー入力を待たなければならない) のを防ぐ呼び出しのタイムアウトはありますか? 誰かに回避策がありますか (たとえば、接続タイムアウトを延長する方法)?UIWebView
のサブクラスよりもHTTP 基本/ダイジェスト認証を処理するためのより良い方法はありNSURLProtocol
ますか?
詳細とコード
処理する必要があるほとんどの SAML システムでは、ログインは通常の Web ページとしてUIWebView
. ただし、処理が必要なシステムの一部は、モバイル ブラウザー用の HTTP 基本認証または HTTP ダイジェスト認証の使用にフォールバックするため、それも処理できる必要があります。
大きな課題は、UIWebView が下にあるネットワーク呼び出しを公開しないという事実から始まります。必要なものを取得するために、NSURLProtocol
必要に応じてサブクラスを作成して登録しました。
[NSURLProtocol registerClass:[SMURLProtocol class]];
これにより、HTTP 基本/認証チャレンジが発行されたときにこのメソッドSMURLProtocol
が呼び出されるので、HTTP 基本認証とダイジェスト認証を処理できる YES を返します。
- (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 を起動して、ユーザーにユーザー ID とパスワードの入力を求めています。ネットワーク コードがバックグラウンド スレッドで実行されているため (どうやら、確かなことはわかりません)、メイン スレッドでそれを行う必要があります。セマフォと明示的な Grand Central Dispatch コードを追加して、時折発生していたクラッシュを回避しました (このスレッドに基づく)。
最後の部分は、ユーザー ID とパスワードを受け入れる 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;
}
}
概要で述べたように、ユーザーがユーザー ID とパスワードを約 10 秒以内に入力できる場合、これはすべてうまく機能します。それよりも時間がかかる場合、接続がタイムアウトしたように見え、認証情報をチャレンジの送信者に渡しても効果がありません。