Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/121.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
iOS/Cocoa-NSURLSession-处理基本HTTPS授权_Ios_Cocoa_Http_Authentication_Nsurlsession - Fatal编程技术网

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

iOS/Cocoa-NSURLSession-处理基本HTTPS授权,ios,cocoa,http,authentication,nsurlsession,Ios,Cocoa,Http,Authentication,Nsurlsession,[编辑以提供更多信息] (我没有将AFNetworking用于此项目。我将来可能会这样做,但希望先解决此问题/误解。) 服务器设置 我不能在这里提供真正的服务,但它是一个简单、可靠的服务,根据URL返回XML,例如: 我想使用GET通过HTTPS连接到URL,并确定任何身份验证失败(http状态代码401) 我已经确认web服务可用,并且我可以使用指定的用户名和密码成功(http状态代码200)从url获取XML。我使用web浏览器、AFNetworking 2.0.3和NSURLConnec

[编辑以提供更多信息]

(我没有将AFNetworking用于此项目。我将来可能会这样做,但希望先解决此问题/误解。)

服务器设置

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

我想使用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: ...
上述代码将起作用。它将成功连接到服务器,获取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++;
}
使用尝试解决方案时出现的问题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")
(NSError还提供了一个NSErrorFailingURLKey,它向我显示URL和凭据是正确的。)


欢迎任何建议

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

长答覆:

  • 如果在没有委托的情况下使用
    NSURLSession
    ,并在URL中包含用户ID/密码,则如果用户ID/密码组合正确,将调用
    NSURLSessionDataTask
    completionHandler
    块。但是,如果身份验证失败,
    NSURLSession
    似乎会重复尝试发出请求,每次都使用相同的身份验证凭据,并且
    completionHandler
    似乎不会被调用。(我注意到,通过观看与的连接)

    在我看来,这并不是非常谨慎的
    NSURLSession
    ,但同样地,无委托格式副本也不能做更多的事情。在使用身份验证时,使用基于
    委托的方法似乎更可靠

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

    另一方面,您似乎在维护自己的
    \u failureCount
    计数器,但您可能会利用
    挑战。而不是以前的failureCount
    属性

  • 也许您可以分享一些关于服务器正在使用的身份验证性质的详细信息。我只会问,因为当我保护web服务器上的目录时,它不会调用
    nsurlsessionelegate
    方法:

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                                 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    
    而是调用
    NSURLSessionTaskDelegate
    方法:

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

  • 正如我所说,您描述的行为与服务器上的身份验证失败一致。共享有关服务器上身份验证设置的性质的详细信息以及
    nsurauthenticationchallenge
    对象的详细信息可能有助于我们诊断发生了什么。您可能还希望在web浏览器中键入带有用户ID/密码的URL,这也可能确认是否存在基本身份验证问题。

    您不需要为此实现委托方法,只需在请求上设置授权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) {
    
     }];
    
    提示的与未提示的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];
    
    希望这将有助于任何遇到这种缺乏记录的差异的人。最后,我使用本地代理测试代码,并在我的项目的
    Info.plist
    文件中强制禁用
    NSAppTransportSecurity
    (在iOS 9/OSX 10.11上通过代理检查SSL流量所必需的)。

    感谢您的帮助