2014-01-14 1 views
13

[отредактированный предоставить больше информации]IOS/Какао - NSURLSession - Обработка Basic HTTPS Авторизация

(я не использую AFNetworking для этого проекта я могу сделать это в будущем, но хочу, чтобы решить эту проблему/недоразумение. первый)

SERVER НАСТРОЙКА

Я не могу обеспечить реальную услугу здесь, но это простой, надежный сервис, который возвращает XML в соответствии с URL, например:.

https://username:[email protected]/webservice

Я хочу подключиться к URL-адресу через HTTPS с помощью GET и определить любые ошибки аутентификации (код состояния http 401).

Я подтвердил, что доступен веб-сервис, и что я могу успешно (http status code 200) захватить XML с URL с использованием указанного имени пользователя и пароля. Я сделал это с помощью веб-браузера и с 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 ПРИ ИСПОЛЬЗОВАНИИ попытка решения

Несмотря на использование правильных учетных данных (как доказано их успешного использования с нулевой делегат), проверка подлинности не удается.

Следующие обратные вызовы происходят:

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 и учетные данные являются правильными.)

Любые предложения приветствуются!

+0

Просьба поделиться сведениями об объекте 'NSURLAuthenticationChallenge'. Вы говорите 'challenge.error' и' challenge.failureResponse' не предоставляются (что я нахожу удивительным). Как насчет 'challenge.protectionSpace'? Или вы говорите «вызов», сам, «ниль»? – Rob

ответ

10

Краткий ответ: поведение, которое вы описываете, согласуется с отказом базовой аутентификации сервера. Я знаю, что вы сообщили, что вы подтвердили, что это правильно, но я подозреваю, что на сервере существует фундаментальная проблема проверки (а не код iOS).

Длинный ответ:

  1. Если вы используете NSURLSession без делегата и включают в себя идентификатор пользователя/пароль в URL, то completionHandler блок NSURLSessionDataTask будет называться, если комбинация идентификатор/пароль правильный. Но, если аутентификация не удалась, NSURLSession, как представляется, неоднократно пытается выполнить запрос, используя одинаковые учетные данные аутентификации каждый раз, и completionHandler, похоже, не вызван. (Я заметил это, наблюдая за соединением с Charles Proxy).

    Это не кажется мне очень осмотрительным по отношению к NSURLSession, но опять же отсутствие делегата не может действительно сделать намного больше. При использовании аутентификации использование подхода, основанного на delegate, кажется более надежным.

  2. Если вы используете NSURLSessiondelegate с указанным (и не completionHandler параметра при создании задачи данных), вы можете изучить природу ошибки в didReceiveChallenge, а именно изучить challenge.error и challenge.failureResponse объектов. Вы можете обновить свой вопрос с этими результатами.

    Как вы считаете, вы поддерживаете свой собственный счетчик _failureCount, но вместо этого вы, вероятно, можете воспользоваться свойством challenge.previousFailureCount.

  3. Возможно, вы можете поделиться некоторыми сведениями о характере аутентификации, которую использует ваш сервер. Я только прошу, потому что когда я защитить папку на моем веб-сервере, он не вызывает NSURLSessionDelegate метод:

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

    Но это, скорее, вызывает NSURLSessionTaskDelegate метод:

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

Как Я сказал, что поведение, которое вы описываете, связано с ошибкой аутентификации на сервере. Совместное использование сведений о характере настройки проверки подлинности на вашем сервере и сведениях о объекте NSURLAuthenticationChallenge могут помочь нам определить, что происходит. Возможно, вы также захотите ввести URL-адрес с идентификатором пользователя/паролем в веб-браузере, и это также может подтвердить, есть ли основная проблема с проверкой подлинности.

+0

Благодарим вас за помощь с этим Робом. Очень признателен! Я отредактировал мой вопрос, чтобы предоставить гораздо больше информации. В частности, беспокойство вызывает отсутствие прогресса в собственности «previousFailureCount». – Womble

+0

@ Rob: Из любопытства, когда вы упомянули о защите каталога на своем сервере, было ли это с помощью простого файла «.htaccess» с базовой аутентификацией? Я сделал это, я никогда не могу получить обратный вызов 'didReceiveChallenge' либо' NSURLSessionDelegate', либо 'NSURLSessionTaskDelegate'. Вместо этого я обрабатываю аутентификацию, поставляя «HTTPAdditionalHeaders» для объекта «NSURLSessionConfiguration». Если я этого не сделаю, я получаю код статуса 404 в ответе заголовка. – Yazid

28

Для этого вам не нужно реализовывать метод делегирования, просто установите 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) { 

}]; 
+2

Прямо к делу. Это просто работает. – KPM

+13

Это работало для меня, но обратите внимание на ссылку на класс: «Классы NSURLConnection и NSURLSession предназначены для обработки различных аспектов HTTP-протокола для вас. В результате вы не должны изменять следующие заголовки: ** Авторизация **, Connection, Host, WWW-Authenticate_ " –

+0

malcolmhall: Это поможет мне с mailgun https AUTH. Thank You Mate – sundsx

19

Повинуясь против Спонтанные HTTP Authentication

Мне кажется, что вся документация по NSURLSession и HTTP Authentication пропускает тот факт, что требование аутентификации может быть запрос (как это происходит при использовании ,htpassword file) или unprompted (как это обычно бывает при работе с услугой 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]; 

Надеюсь, это поможет любому, кто столкнется с этой плохо документированной разницей. Я, наконец, понял это с помощью тестового кода, локального прокси ProxyApp и принудительного отключения NSAppTransportSecurity в файле проекта моего проекта (необходим для проверки трафика SSL через прокси-сервер в iOS 9/OSX 10.11).

Смежные вопросы