追記 09/01/2012
ごめんちょっと読み替えしてみたら試行錯誤した過程で、結局どーやるんだよという文章になってた。
簡潔に以下にまとめます。
アプリ起動直後に以下の configureCredential を呼び出すようにしてください。各文字列はあなたの環境のものと置き換えてください。
- (void)configureCredential { // - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions // などから呼べばよし NSURLCredential* creds = [NSURLCredential credentialWithUser:@"USER" password:@"PASSWORD" persistence:NSURLCredentialPersistenceForSession]; NSURLCredentialStorage* store = [NSURLCredentialStorage sharedCredentialStorage]; NSURLProtectionSpace* protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"example.jp" port:80 protocol:@"http" realm:@"Please enter your ID and password" authenticationMethod:NSURLAuthenticationMethodDefault]; [store setCredential:creds forProtectionSpace:protectionSpace]; }
realm の部分はたとえば以下のコマンドで確認できます。
curl -Iv 'http://www.chama.ne.jp/htaccess_sample/index.htm' ... # WWW-Authenticate: Basic realm="Input ID and Password." ...
以上。
以下は初稿
- UIWebViewでBASIC認証のあるページにアクセスする方法 - 24/7 twenty-four seven (旧来の方法)
- A-Liaison BLOG: UIWebView の Private API を使って BASIC認証のあるページにアクセスする (App Store 出せない)
- UIWebView を使って BASIC認証のあるページにアクセスする — Gist (動かん)
- なんとなく。なにげなく。 UIWebViewでベーシック認証のサイトに対応させる方法がマジで分からない件
今までは苦し紛れにホスト部分を動的に "user:password@example.com" などに書き換えていた。そんな日々ともお別れです。
サンプルコードは以下。2とうりの方法で実装した→5パターンに増えた。
https://github.com/laiso/iOSSamples/tree/master/UIWebViewAuthentication
(追記)検索エンジンからここへ来た人向け
UIWebView delegate 内で処理する
だいたい以下のdelegate で実装している。ちなみにパスワード間違えてる時などUIWebViewDelegate のwebViewDidFinishLoadl: で受け取れない仕様なのがアレである(タイムアウトで対応?)。
// UIWebViewAuthentication/DetailViewController.m #pragma mark - UIWebViewDelegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if([request valueForHTTPHeaderField:@"Authorization"]){ return YES; } NSMutableURLRequest* req = (NSMutableURLRequest*)request; NSString* authToken = [NSString stringWithFormat:@"%@:%@", USER, PASSWORD]; GTMStringEncoding *coder = [GTMStringEncoding rfc4648Base64WebsafeStringEncoding]; [req addValue:[NSString stringWithFormat:@"Basic %@", [coder encodeString:authToken]] forHTTPHeaderField:@"Authorization"]; [webView loadRequest:req]; return NO; }
NSURLConnection のdelegate でNSURLCredential をセットする
NSURLCredential で設定しておくことでもいいらしい。
やってみた。ちょっと冗長になったけど以下のよう。UIWebView がアクセスする前にNSURLConnection で対象のサーバへのリクエストを発行して認証関連のdelegate が呼ばれるのでそこでBasic 認証のNSURLCredential をセットする感じ。
// USENSURLCredential/USENSURLCredentialViewController.m - (void)viewDidLoad { [super viewDidLoad]; [self registerMyCredential]; //[self configureView]; } - (void)registerMyCredential { NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:DEFAULT_URL]] delegate:self]; [conn start]; } - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { NSURLCredential* creds = [NSURLCredential credentialWithUser:USER password:PASSWORD persistence:NSURLCredentialPersistencePermanent]; [[challenge sender] useCredential:creds forAuthenticationChallenge:challenge]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self configureView]; } - (void)configureView { NSString* url = DEFAULT_URL; [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]]; }
こっちのが元からあるFoundation の仕組みを利用していて自然ではあるが、2回リクエストが飛ぶ?
このNSURLCredential を使った認証ではパスワードなどは自動的にOS内のキーチェーンなどのセキュアストレージに保存されるらしく。認証が成功したらその後はNSURLCredential を発行しなくても閲覧できるようなった。
お、じゃあNSURLCredential を使った方がいいかな? というとおそらくURL にパスワード含ませた時もヘッダいじった時も内部的に同じ処理をしていると思われる。のでどれがいいのかはよくわからず。
この件 stackoverflow.com とかでも既出ではないみたいなんで見掛けたら回答しておくつもり。
さらなる方法
NSURLCredentialStorageを直接使えるかもという情報を教えてもらったので確認している。
あとNSURLCredentialPersistencePermanent ってやっていることから、サンドボックス外に認証情報が保存されているなら一端iOSを初期化しないと確認できないね。
CredentialStorage の初期化
iOS初期化(シミュレータでいう'Reset Content and Setting')めんどくさいので、起動直後にこうやった。
// clean NSURLCredentialStorage* store = [NSURLCredentialStorage sharedCredentialStorage]; [[store allCredentials] enumerateKeysAndObjectsUsingBlock:^(NSURLProtectionSpace* space, NSDictionary* credHash, BOOL *stop) { NSURLCredential* cred = [credHash objectForKey:USER]; [store removeCredential:cred forProtectionSpace:space]; }];
URLCredentialStorageに直接保存しちゃえば?
(というかよく見たら先のAkimbo App Studio のエントリに書いてあった……)
こうやってみた。が結果これは認証成功しない。
修正したらできた。url.port がnil なのに気付いていなかったようだ
- (void)setCredential { NSURLCredential* creds = [NSURLCredential credentialWithUser:USER password:PASSWORD persistence:NSURLCredentialPersistenceForSession]; NSURLCredentialStorage* store = [NSURLCredentialStorage sharedCredentialStorage]; NSURL* url = [NSURL URLWithString:DEFAULT_URL]; NSURLProtectionSpace* protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:url.host port:80 protocol:url.scheme realm:@"Input ID and Password." authenticationMethod:NSURLAuthenticationMethodDefault]; [store setCredential:creds forProtectionSpace:protectionSpace]; }
これは先に言われてるとうりREALM の一致が必須だった。REALM 知らないという場合、Xcode環境あるなら以下のコマンドで調べられる
curl -Iv 'http://www.chama.ne.jp/htaccess_sample/index.htm' ... # WWW-Authenticate: Basic realm="Input ID and Password." ...
レスポンスヘッダの"Input ID and Password." の部分がそれ。
UIWebView のリクエストを書き換えて認証したパターンではURLCredentialStorage に保存されていない?
デバッグの過程で気付いたんだけど、一番目のUIWebView のリクエスを書き換えパターンで認証通した場合 [store allCredentials] に出てこない……
どこか別の場所で管理しているのか。とりあえずわかった事実は以下
- Mobile Safari で同じページにアクセスしたところ未承認(認証前)だった
- URLCredentialStorage に保存されていればSafari でもアクセスできる
- アプリA とアプリB の二つにわけて共有されるか確認したが、共有されていなかった
- URLCredentialStorage に保存されていればアプリA とアプリB でもアクセスできる
なぞが多い……。”URLCredentialStorageに直接保存” ができれば一番いいんだけど。→できた
もう一個わかった
- " NSURLConnection のdelegate でNSURLCredential をセットする" のあとUIWebView で一度承認されるとURLCredentialStorage のデータを削除してもひき続き閲覧できる
つまりはUIWebView でBASIC 認証を通した時の保存先がなんかおかしい。ということ以上わからず。
まとめ: UIWebView はおかしい
iOS向けアプリ開発者がハマるの諸悪の根源はUIWebView の実装であり。業界の不況もすべてはUIWebView の実装のせいだと思っています。