iOS:HTTP Basic/Digest Auth和UIWebView

iOS:HTTP Basic/Digest Auth和UIWebView,ios,objective-c,uiwebview,nsurlconnectiondelegate,nsurlprotocol,Ios,Objective C,Uiwebview,Nsurlconnectiondelegate,Nsurlprotocol,概述 我正在为iOS应用程序开发SAML登录(单点登录,类似于openID)解决方案,该解决方案涉及使用UIWebView显示视图控制器,并且在UIWebView中处理HTTP basic/digest auth时遇到计时和/或超时问题 具体来说,当客户端收到HTTP身份验证质询时,我会弹出一个UIAlertView,提示用户输入用户名和密码。如果用户能够快速输入信息(

概述

我正在为iOS应用程序开发SAML登录(单点登录,类似于openID)解决方案,该解决方案涉及使用
UIWebView
显示视图控制器,并且在
UIWebView
中处理HTTP basic/digest auth时遇到计时和/或超时问题

具体来说,当客户端收到HTTP身份验证质询时,我会弹出一个
UIAlertView
,提示用户输入用户名和密码。如果用户能够快速输入信息(<10秒),则它可以工作。但是,如果条目花费的时间超过10秒,则连接似乎已终止,并且什么也没有发生

问题

  • 调用
    连接:didReceiveAuthenticationChallenge:
    是否有超时,这会阻止我提示用户输入用户名和密码(并且必须等待用户输入)?是否有人有解决方法(例如,通过某种方式延长连接超时)
  • UIWebView
    处理HTTP基本/摘要身份验证是否有比
    NSURLProtocol
    的子类更好的方法
  • 详细信息和代码

    对于我们需要处理的大多数SAML系统,登录将作为常规网页显示在
    UIWebView
    中。然而,我们需要处理的一些系统会退回到对移动浏览器使用HTTP basic或HTTP digest身份验证,因此我们也需要能够处理这个问题

    最大的挑战始于UIWebView不公开其下的网络调用这一事实。为了获得所需内容,我创建了
    NSURLProtocol
    的子类,并根据需要进行了注册:

    [NSURLProtocol registerClass:[SMURLProtocol class]];
    
    这样,当发出HTTP basic/auth质询时,
    SMURLProtocol
    上的此方法将被调用,因此我返回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);
    
    }
    }
    

    正如我在概述中所说的,如果用户能够在大约10秒内输入userID和密码,那么这一切都非常有效。如果需要更长时间,则连接似乎超时,将凭据传递给质询的发送者无效。

    您是否得到过答复?除了AFNetworking(它只是NSURLConnection上的一个漂亮的包装器)之外,我也有同样的问题。在经历了许多惊愕之后,我发现在UIWebView内部(即在应用程序内部)执行SAML身份验证是一项非常简单的任务。更干净、更健壮、但用户友好的方法是利用Safari为您实现这一点。基本上,您可以使用身份验证URL启动Safari,服务完成后,它将使用自定义URL方案启动回您的应用程序,并将身份验证令牌作为URL参数传递。苹果目前对OAuth/SAML身份验证流的官方建议是使用
    SFSafariViewController
    (在iOS 9中引入). 但是,大多数执行OAuth/SAML的应用程序(包括我们的应用程序)仍在使用Safari,因为它是处理许多企业中的奇怪身份验证配置(例如自签名证书、摘要身份验证等)的最可靠、最健壮的方法。
    - (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;
    }
    }