NSURLSession с backgroundSessionConfiguration не может работать с сертификатом пользователя

Я нашел проводную вещь о 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/contents2014-08-11 15:36: 01.232 OneBox [1736: 1413] ClientCert: https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents2014-08-11 15: 36: 02.068 OneBox [1736: 8c03] ServerTrust: https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents2014-08-11 15: 36: 02.076 OneBox [1736: 1413] ClientCert: https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents2014-08-11 15: 36: 12.728 OneBox [1736: 1413] ServerTrust: https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents2014-08-11 15: 36: 12.735 OneBox [1736: 1413] ClientCert: https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents

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, Иначе это не сработает.

Последнее замечание В зависимости от вашего варианта использования, недостатком использования этого хака является то, что вы можете оказаться с кучей постоянно сохраняемых учетных данных, если несколько NSURLProtectionSpaces, Возможно, вам придется заняться уборкой после настройки defaultCredential для NSURLCredentialStorage учебный класс. Это выходит за рамки этого ответа, но у класса есть несколько удобных методов, таких как -removeCredential:forProtectionSpace: которые описаны здесь https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCredentialStorage_Class/

Другие вопросы по тегам