NSURLSessionでBasic認証する
どうせ1つのアプリで複数のBasic認証することなんて無いっしょ
とか思いつつも使い回しがききそうな実装を考えたのでメモ
- requestのURLは
http://user:[email protected]
みたいにユーザとパスワードも記載しておくとする NSURLSessionTask
のcompletionHandler
は使わないでdelegate
を使用するWWW-Authenticate
のrealm
を取得- 認証するURLに対して
curl -v
とかリクエストして401レスポンスを取得 - 今回はこんなレスポンスが来ていた
WWW-Authenticate: Basic realm="Login Required"
- 該当
realm
はLogin Required
delegate
メソッド- URLSession:didReceiveChallenge:completionHandler:
を実装する- URLSession:didReceiveChallenge:completionHandler:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
/*
Basic認証処理
*/
NSURL *url = task.originalRequest.URL;
// 認証情報ストレージを取得
NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
// 認証情報ストレージのキーとなるNSURLProtectionSpaceを作成
NSURLProtectionSpace *ps = [[NSURLProtectionSpace alloc] initWithHost:url.host
port:[url.port integerValue]
protocol:url.scheme
realm:@"Login Required"
authenticationMethod:NSURLAuthenticationMethodDefault];
// 認証情報を取得
NSURLCredential *credential = [storage defaultCredentialForProtectionSpace:ps];
if (!credential) {
// 認証情報を作成、保存する
credential = [[NSURLCredential alloc] initWithUser:url.user
password:url.password
persistence:NSURLCredentialPersistenceForSession];
[storage setCredential:credential forProtectionSpace:ps];
}
if ([challenge previousFailureCount]>0) {
// 1度でも認証失敗している場合はキャンセルする
[task cancel];
[session invalidateAndCancel];
}
else {
// 認証リクエスト
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengeUseCredential;
completionHandler(disposition, credential);
}
}
追記
basic認証の必要なAPIリクエストを並列実行していたら- setCredential:forProtectionSpace:
でクラッシュしたので多分書き込みバッティングかも
対策としてはNSOperationQueue
を使ってもよかったけど、それはそれで面倒だったのでdispatch_queue_t
を返すクラスメソッド作ってdispatch_barrier_async
で実行してあげて無理矢理同期っぽいバッティング回避策をすることをした(適当)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
dispatch_barrier_async([[self class] queue], [^{
// 認証処理
});
}
+ (dispatch_queue_t)queue {
static dispatch_queue_t queue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
queue = dispatch_queue_create([[NSString stringWithFormat:@"%@.network", bundleId] UTF8String], NULL);
});
return queue;
}