16

[より多くの情報を提供するために編集]

(私はこのプロジェクトで AFNetworking を使用していません。将来的に使用する可能性がありますが、最初にこの問題/誤解を解決したいと考えています。)

サーバーのセットアップ

ここで実際のサービスを提供することはできませんが、次のような URL に従って XML を返すシンプルで信頼性の高いサービスです。

https://username:password@example.com/webservice

GET を使用して HTTPS 経由で URL に接続し、認証の失敗 (http ステータス コード 401) を確認したいと考えています。

Web サービスが利用可能であること、および指定されたユーザー名とパスワードを使用して URL から XML を正常に取得できること (http ステータス コード 200) を確認しました。これは、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

ivar _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 と資格情報が正しいことを示しています。)

どんな提案でも大歓迎です!

4

3 に答える 3

30

このためにデリゲート メソッドを実装する必要はありません。リクエストに認証 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) {

 }];
于 2014-08-08T23:08:57.207 に答える
22

プロンプト付きとプロンプトなしの HTTP 認証

NSURLSession と HTTP 認証に関するすべてのドキュメントは、認証の要件がプロンプト表示される ( .htpasswordファイルを使用する場合のように) か、プロンプト表示されない ( REST サービスを扱う場合の通常の場合のように) という事実をスキップしているように私には思えます。 )。

プロンプトが表示された場合、正しい戦略はデリゲート メソッドを実装することです URLSession:task:didReceiveChallenge:completionHandler:。プロンプトが表示されない場合、デリゲート メソッドの実装は、SSL チャレンジ (保護スペースなど) を検証する機会のみを提供します。したがって、REST を扱うときは、@malhal が指摘したように、Authentication ヘッダーを手動で追加する必要がある可能性があります。

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];

うまくいけば、これは文書化されていないこの違いに遭遇した人の助けになるでしょう. テストコード、ローカルプロキシProxyAppNSAppTransportSecurityを使用し、プロジェクトのファイルで強制的に無効にすることで最終的に解決しましたInfo.plist(iOS 9/OSX 10.11 のプロキシを介して SSL トラフィックを検査するために必要です)。

于 2015-10-11T00:20:32.020 に答える
10

短い答え:あなたが説明する動作は、基本的なサーバー認証の失敗と一致しています。あなたがそれが正しいことを確認したと報告したことは承知していますが、サーバー (iOS コードではなく) に基本的な検証の問題があると思われます。

長い答え:

  1. NSURLSessionデリゲートなしで使用し、URL にユーザー ID/パスワードを含めると、ユーザー ID/パスワードの組み合わせが正しい場合にcompletionHandlerのブロックが呼び出されます。NSURLSessionDataTaskただし、認証が失敗した場合、 はNSURLSession毎回同じ認証資格情報を使用して繰り返し要求を試みているようにcompletionHandler見え、 は呼び出されないように見えます。( Charles Proxyとの接続を見て気づいた)。

    これは私にはあまり賢明ではありませんNSURLSessionが、デリゲートのないレンディションはそれ以上のことはできません。認証を使用する場合、delegateベースのアプローチを使用すると、より堅牢に見えます。

  2. 指定したを使用しNSURLSessionて(データ タスクの作成時にパラメータを指定せずに) を使用すると、 でエラーの性質を調べることができます。つまり、およびオブジェクトを調べることができます。これらの結果で質問を更新することをお勧めします。delegatecompletionHandlerdidReceiveChallengechallenge.errorchallenge.failureResponse

    余談ですが、あなたは自分のカウンターを維持しているように見えますが、代わり_failureCountに財産を利用できる可能性があります。challenge.previousFailureCount

  3. おそらく、サーバーが使用している認証の性質に関する詳細を共有できます。NSURLSessionDelegateWeb サーバーでディレクトリを保護するときに、メソッドが呼び出されないため、質問するだけです。

    - (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何が起こっているのかを診断するのに役立つ場合があります。また、Web ブラウザーでユーザー ID/パスワードを使用して URL を入力することもできます。これにより、基本認証の問題があるかどうかを確認することもできます。

于 2014-01-14T07:29:02.773 に答える