【问题标题】:Can I use NSURLCredentialStorage for HTTP Basic Authentication?我可以使用 NSURLCredentialStorage 进行 HTTP 基本身份验证吗?
【发布时间】:2010-10-04 19:31:26
【问题描述】:

我设置了一个可可类,我想用它来连接我正在构建的 RESTful Web 服务。我决定像这样在我的 PHP 后端使用 HTTP 基本身份验证……

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    //Stuff that users will see if they click 'Cancel'
    exit;
}
else {
    //Validation Code
    echo "You entered info.";
}
?>

此时我正在使用同步 NSURLConnection,据我了解,Apple 文档状态对身份验证的支持较少。

但这有可能吗?我可以很容易地进行 cookie 身份验证,无需 NSURLProtectionSpaces 或 NSURLCredentials 或任何身份验证类。另外,是否有任何资源可以让我了解更多关于 Cocoa 身份验证类的信息?

谢谢。

更新:mikeabdullahuk 您提供的代码(第二个示例)与我编写的代码几乎相同。我做了更多调查,发现 NSURLConnection 正在返回一个错误……

Error Domain=NSURLErrorDomain Code=-1012 UserInfo=0x1a5170 "Operation could not be completed. (NSURLErrorDomain error -1012.)"

代码对应NSURLErrorUserCancelledAuthentication。所以显然我的代码没有访问 NSURLCredentialStorage 而是取消了身份验证。这可能与 PHP HTTP 身份验证功能有关吗?在这一点上我很困惑。

【问题讨论】:

    标签: objective-c cocoa http authentication https


    【解决方案1】:

    同步的NSURLConnection 绝对可以与NSURLCredentialStorage 一起使用。以下是通常的工作方式:

    1. NSURLConnection 向服务器请求页面
    2. 服务器回复 401 响应
    3. NSURLConnection 查看它可以从 URL 中收集哪些凭据
    4. 如果网址没有提供完整的凭据(用户名和密码),NSURLConnection 还将咨询NSURLCredentialStorage 以填补空白
    5. 如果完整凭据仍然尚未确定,NSURLConnection 将发送 -connection:didReceiveAuthenticationChallenge: 委托方法请求凭据
    6. 如果 NSURLConnection 现在终于拥有完整凭据,它会重试包含授权数据的原始请求。

    通过使用同步连接方法,您只会失去第 5 步,即提供自定义身份验证的能力。因此,您可以在 URL 中预先提供身份验证凭据,也可以在发送请求之前将它们放在 NSURLCredentialStorage 中。例如

    NSURLRequest *request =
      [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://user:pass@example.com"]];
    
    [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
    

    或:

    NSURLCredential *credential = [NSURLCredential credentialWithUser:@"user"
                                                             password:@"pass"
                                                          persistence:NSURLCredentialPersistenceForSession];
    
    NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
      initWithHost:@"example.com"
      port:0
      protocol:@"http"
      realm:nil
      authenticationMethod:nil];
    
    
    [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
                                                        forProtectionSpace:protectionSpace];
    [protectionSpace release];
    
    NSURLRequest *request =
      [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com"]];
    
    [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
    

    【讨论】:

    • 我刚遇到这个。我对ObjectiveC 非常非常,但我想知道是否必须释放任何对象(特别是凭据和保护空间)。顺便说一句,我在异步调用(initWithRequest)上实现了这个方法,仍然得到了 didReceiveAuthenticationChallenge 回调。
    • @Bill,如果在生产代码中使用 protectionSpace 确实需要释放,因为我使用了 +alloc。
    • protectionSpace 必须被释放。 alloc 将返回一个具有 retainCount == 1 的对象,因此在 [... setDefaultCredential:...] 之后您应该像 [protectionSpace release]; 一样释放它
    • NSURLCredentialStorage 对我不起作用,但只需在 http 字符串中为服务器添加前缀 user:pass 确实有效,非常感谢,超级简单的解决方案。
    • 根据 sendSynchronousRequest 的苹果文档:'如果需要身份验证才能下载请求,则必须将所需凭据指定为 URL 的一部分......'我自己的经验会证实这一点;也就是说,使用 NSURLCRedentialStorage 不适用于 sendSynchronousRequest。
    【解决方案2】:

    在 401 或其他身份验证挑战不可接受/不可能的情况下,我有时会使用虚拟 CFHTTPMessage 生成身份验证行,然后将其复制回 NSURLRequest:

    // assume NSString *username and *password exist and NSURLRequest *urlRequest
    // exists and is fully configured except for HTTP Basic Authentication.. 
    
    CFHTTPMessageRef dummyRequest =
        CFHTTPMessageCreateRequest(
            kCFAllocatorDefault,
            CFSTR("GET"),
            (CFURLRef)[urlRequest URL],
            kCFHTTPVersion1_1);
    CFHTTPMessageAddAuthentication(
        dummyRequest,
        nil,
        (CFStringRef)username,
        (CFStringRef)password,
        kCFHTTPAuthenticationSchemeBasic,
        FALSE);
    authorizationString =
        (NSString *)CFHTTPMessageCopyHeaderFieldValue(
            dummyRequest,
            CFSTR("Authorization"));
    CFRelease(dummyRequest);
    
    [urlRequest setValue:authorizationString forHTTPHeaderField:@"Authorization"];
    

    这可能看起来完全是一种奇怪的方法,但它可以容忍用户名/密码不是 URL 干净的情况以及 NSURLRequest 拒绝查询 NSURLCredentialStorage 因为服务器实际上没有发送 HTTP 401(对于例如它会发送一个常规页面)。

    【讨论】:

    • 谢谢!它在不希望使用委托模式的情况下特别有用。
    【解决方案3】:

    我会注意到 mikeabdullahuk 的回答很好,但是如果您使用 NSURLCredentialPersistencePermanent 而不是每个会话,它会将凭据存储在用户钥匙串中,因此下次您可以检查 NSURLCredentialStorage 以获取保护空间的默认凭据的非零值和如果你得到一个非零值,你可以只传递凭据。我现在正在为我正在编写的一个美味的 com 客户端使用这种方法,它在我的测试中运行良好。

    【讨论】:

      【解决方案4】:

      将您的凭据设置为保护空间的默认凭据:

      // Permananent, session, whatever.
      NSURLCredential *credential = [NSURLCredential credentialWithUser:username password:password persistence: NSURLCredentialPersistencePermanent];
      // Make sure that if the server you are accessing presents a realm, you set it here.
      NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"blah.com" port:0 protocol:@"http" realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
      // Store it
      [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
      

      此时,使用与您设置的保护空间相匹配的任何后续 NSURLConnection 都将使用此凭据

      【讨论】:

        猜你喜欢
        • 2010-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-07
        • 2017-05-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多