带有backgroundSessionConfiguration的NSURLSession无法使用用户证书

时间:2014-08-11 07:56:47

标签: ios nsurlsession nsurlcredential

在使用后台会话配置时,我发现了有关NSURLSession的有线事。 我们在与服务器联系时使用自签名证书,这是一个工具:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
            NSLog(@"ServerTrust:%@", task.originalRequest.URL);
        }  else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
            if (self.clientCertCredential && [challenge previousFailureCount] == 0) {
                credential = self.clientCertCredential;
                disposition = NSURLSessionAuthChallengeUseCredential;
                NSLog(@"ClientCert:%@", task.originalRequest.URL);
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

使用defaultSessionConfiguration时,它工作正常,但是当我将会话配置更改为后台会话配置时,将在循环中调用此委托方法,将不会调用其他任何委托方法,并且此请求将永远不会完成。

这是控制台输出:

2014-08-11 15:36:01.204 OneBox [1736:a413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:01.232 OneBox [1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:02.068 OneBox [1736:8c03] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:02.076 OneBox [1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:12.728 OneBox [1736:1413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:12.735 OneBox [1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents

1 个答案:

答案 0 :(得分:1)

好的,我知道这是一个古老的问题,但我也在这个问题上一直在敲打墙壁,所以希望这会对某人有所帮助,这是我解决问题的方法。 公平地说,我的“解决方案”对我来说更像是一个错综复杂的解决方案,而不是一个合适的解决方案,但至少它是有效的。

简短回答是让它发挥作用的关键是使用NSURLProtectionSpace为所有会话设置默认永久凭据。这可以防止在呈现NSURLAuthenticationMethodClientCertificate类型的质询时调用委托。

长答案如下所示。

在您的代码中,这不起作用:

credential = self.clientCertCredential;
disposition = NSURLSessionAuthChallengeUseCredential;
//
// Redacted for clarity
//
completionHandler(disposition, credential);

因为在后台会话中,代理人无法访问self.clientCertCredential(上帝只知道原因)。

但是我发现如果您之前在NSURLProtectionSpace中定义了默认凭据,后台会话将不会尝试调用该委托。

请抓住所有else if块,然后执行以下操作:

NSURLProtectionSpace *space = [NSURLProtectionSpace 
    initWithHost:@"your_address"
    port:your_port
    protocol:@"https"
    realm:@"your_realm"
    authenticationMethod:NSURLAuthenticationMethodClientCertificate];
[[NSURLCredentialStorage sharedCredentialStorage] 
    setDefaultCredential:self.clientCertCredential
    forProtectionSpace:space];

如果主机端口参数与服务器的参数完全匹配,那么当质询提交给后台会话时, challenge.protectionSpace将自动找到默认凭据。

为了使其工作,在尝试使用后台会话发出任何请求之前,需要执行此代码。例如,只要将客户端证书加载到self.clientCertCredential,就可以执行此操作。

但要小心!这里还有一个细微之处。每当您这样做时,请确保使用持久性选项NSURLCredentialPersistencePermanent加载证书。否则它将无法工作。

最后一点。根据您的使用情况,使用此hack的缺点是,如果多个NSURLProtectionSpaces,您可能会发现自己拥有一堆永久持久的凭据。在为defaultCredential课程设置NSURLCredentialStorage后,您可能需要做一些内务管理。这超出了本答案的范围,但是该类有一些方便的方法,例如-removeCredential:forProtectionSpace:,这里记录了https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCredentialStorage_Class/