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/