iOS/Cocoa - NSURLSession - 处理基本的HTTPS授权

Wom*_*ble 16 authentication cocoa http ios nsurlsession

[编辑提供更多信息]

(我没有在这个项目中使用AFNetworking.我可能会在将来这样做,但希望先解决这个问题/误解.)

服务器设置

我不能在这里提供真正的服务,但它是一个简单,可靠的服务,它根据URL返回XML,例如:

https://开头的用户名:password@example.com/webservice

我想使用GET通过HTTPS连接到URL,并确定任何身份验证失败(http状态代码401).

我已经确认Web服务可用,并且我可以成功(http状态代码200)使用指定的用户名和密码从URL中获取XML.我使用Web浏览器和AFNetworking 2.0.3以及使用NSURLConnection完成了这项工作.

我还确认我在所有阶段使用正确的凭据.

鉴于正确的凭据和以下代码:

// Note: NO delegate provided here.
self.sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfig
                                  delegate:nil
                             delegateQueue:nil];

NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:self.requestURL     completionHandler: ...
Run Code Online (Sandbox Code Playgroud)

上面的代码将起作用.它将成功连接到服务器,获取http状态代码200,并返回(XML)数据.

问题1

在凭证无效的情况下,这种简单方法会失败.在这种情况下,永远不会调用完成块,不提供状态代码(401),最终任务超时.

经过尝试的解决方案

我为NSURLSession分配了一个委托,并处理以下回调:

-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    if (_sessionFailureCount == 0) {
        NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone];        
    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
    } else {
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
    _sessionFailureCount++;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,    NSURLCredential *credential))completionHandler
{
    if (_taskFailureCount == 0) {
        NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone];        
        completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
    } else {
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
    _taskFailureCount++;
}
Run Code Online (Sandbox Code Playgroud)

问题1使用经过尝试的解决方案

请注意使用ivars _sessionFailureCount和_taskFailureCount.我正在使用这些因为挑战对象的@previousFailureCount属性永远不会提升!无论调用这些回调方法多少次,它始终保持为零.

问题2使用经过尝试的解决方案

尽管使用了正确的凭证(通过成功使用nil委托证明了这一点),但身份验证失败了.

发生以下回调:

URLSession:didReceiveChallenge:completionHandler:
(challenge @ previousFailureCount reports as zero)
(_sessionFailureCount reports as zero)
(completion handler is called with correct credentials)
(there is no challenge @error provided)
(there is no challenge @failureResponse provided)


URLSession:didReceiveChallenge:completionHandler:
(challenge @ previousFailureCount reports as **zero**!!)
(_sessionFailureCount reports as one)
(completion handler is called with request to cancel challenge)
(there is no challenge @error provided)
(there is no challenge @failureResponse provided)

// Finally, the Data Task's completion handler is then called on us.
(the http status code is reported as zero)
(the NSError is reported as NSURLErrorDomain Code=-999 "cancelled")
Run Code Online (Sandbox Code Playgroud)

(NSError还提供了一个NSErrorFailingURLKey,它向我显示URL和凭据是正确的.)

欢迎任何建议!

mal*_*hal 30

您不需要为此实现委托方法,只需在请求上设置授权HTTP标头,例如

NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://whatever.com"]];

NSString *authStr = @"username:password";
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat: @"Basic %@",[authData base64EncodedStringWithOptions:0]];
[request setValue:authValue forHTTPHeaderField:@"Authorization"];

//create the task
NSURLSessionDataTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

 }];
Run Code Online (Sandbox Code Playgroud)

  • 这对我有用,但请注意类引用说:"_ NSURLConnection类和NSURLSession类旨在为您处理HTTP协议的各个方面.因此,您不应修改以下标头:**授权**,连接,主机,WWW-Authenticate_" (13认同)
  • @CarlLee你反对文档的地方存在更大的风险,即后来的更改会导致问题所以值得关注,尽管有时候(在这种情况下似乎)仍然是正确的事情.应该有适当的API(或支持此用途的文档中的更改)以提前提供授权信息. (4认同)

mar*_*ler 21

提示与未提示的HTTP身份验证

在我看来,关于NSURLSession和HTTP身份验证的所有文档都跳过了以下事实:可以提示身份验证要求(如使用.htpassword文件时的情况)或者是自(在处理REST服务时通常的情况) ).

对于提示的情况下,正确的策略是实现委托方法: URLSession:task:didReceiveChallenge:completionHandler:; 对于自发的情况,委托方法的实现只会为您提供验证SSL质询的机会(例如保护空间).因此,在处理REST时,您可能需要手动添加身份验证标头,如@malhal指出的那样.

这是一个更详细的解决方案,可以跳过创建NSURLRequest.

  //
  // REST and unprompted HTTP Basic Authentication
  //

  // 1 - define credentials as a string with format:
  //    "username:password"
  //
  NSString *username = @"USERID";
  NSString *password = @"SECRET";
  NSString *authString = [NSString stringWithFormat:@"%@:%@",
    username,
    secret];

  // 2 - convert authString to an NSData instance
  NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding];

  // 3 - build the header string with base64 encoded data
  NSString *authHeader = [NSString stringWithFormat: @"Basic %@",
    [authData base64EncodedStringWithOptions:0]];

  // 4 - create an NSURLSessionConfiguration instance
  NSURLSessionConfiguration *sessionConfig =
    [NSURLSessionConfiguration defaultSessionConfiguration];

  // 5 - add custom headers, including the Authorization header
  [sessionConfig setHTTPAdditionalHeaders:@{
       @"Accept": @"application/json",
       @"Authorization": authHeader
     }
  ];

  // 6 - create an NSURLSession instance
  NSURLSession *session =
    [NSURLSession sessionWithConfiguration:sessionConfig delegate:self
       delegateQueue:nil];

  // 7 - create an NSURLSessionDataTask instance
  NSString *urlString = @"https://API.DOMAIN.COM/v1/locations";
  NSURL *url = [NSURL URLWithString:urlString];
  NSURLSessionDataTask *task = [session dataTaskWithURL:url
                                  completionHandler:
                                  ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
                                    if (error)
                                    {
                                      // do something with the error

                                      return;
                                    }

                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                                    if (httpResponse.statusCode == 200)
                                    {
                                      // success: do something with returned data
                                    } else {
                                      // failure: do something else on failure
                                      NSLog(@"httpResponse code: %@", [NSString stringWithFormat:@"%ld", (unsigned long)httpResponse.statusCode]);
                                      NSLog(@"httpResponse head: %@", httpResponse.allHeaderFields);

                                      return;
                                    }
                                  }];

  // 8 - resume the task
  [task resume];
Run Code Online (Sandbox Code Playgroud)

希望这将有助于任何遇到这种记录不良差异的人.我终于使用测试代码,本地代理ProxyAppNSAppTransportSecurity在我的项目Info.plist文件中强制禁用(通过iOS 9/OSX 10.11上的代理检查SSL流量所必需).


Rob*_*Rob 10

简短回答:您描述的行为与基本服务器身份验证失败一致.我知道你已经报告过你已经确认它是正确的,但我怀疑服务器上有一些基本的验证问题(不是你的iOS代码).

答案很长:

  1. 如果您在NSURLSession没有委托的情况下使用并在URL中包含用户标识/密码,那么如果用户标识/密码组合正确completionHandler,NSURLSessionDataTask则将调用该标记块.但是,如果身份验证失败,NSURLSession似乎反复尝试发出请求,每次都使用相同的身份验证凭据,并且completionHandler似乎没有被调用.(我注意到通过观察与Charles Proxy的连接).

    这并没有让我觉得非常谨慎NSURLSession,但是再一次,无委托的表现不能真正做到这一点.使用身份验证时,使用delegate基于-Based的方法看起来更健壮.

  2. 如果使用NSURLSession带有delegate指定的(并且completionHandler在创建数据任务时没有参数),则可以检查错误的性质didReceiveChallenge,即检查challenge.errorchallenge.failureResponse对象.您可能希望使用这些结果更新您的问题.

    顺便说一句,你似乎在维护自己的_failureCount柜台,但你可以利用自己的challenge.previousFailureCount财产.

  3. 也许您可以分享一些有关服务器使用的身份验证性质的详细信息.我只是问,因为当我在我的Web服务器上保护目录时,它不会调用该NSURLSessionDelegate方法:

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                                 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    
    Run Code Online (Sandbox Code Playgroud)

    而是,它调用NSURLSessionTaskDelegate方法:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    
    Run Code Online (Sandbox Code Playgroud)

就像我说的那样,您描述的行为包括服务器上的身份验证失败.共享有关服务器上的身份验证设置的详细信息以及NSURLAuthenticationChallenge对象的详细信息可能有助于我们诊断正在进行的操作.您可能还希望在Web浏览器中键入带有用户ID /密码的URL,这可能还会确认是否存在基本身份验证问题.