如何在UIWebView中正确地进行身份validation?

我想在我的UIWebView中支持HTTP基本authentication。

目前,我正在取消请求

webView:shouldStartLoadWithRequest:navigationType:然后在我自己的NSURLConnectionDelegate中处理它们以检查并提供凭据(如果需要的话)。 然后我使用loadData:MIMEType:textEncodingName:baseURL:在Web视图中呈现HTML。 这适用于任何传递给委托的url。

我的问题是,委托从来没有被要求embedded式元素,如图像,JavaScript或CSS文件。 所以如果我有一个HTML页面引用了一个被基本authentication保护的图片,那么这个图片就不能被正确加载。 此外, webView:didFinishLoad:永远不会被调用,因为Web视图无法完全加载页面。

我已经通过App Store上的第三方浏览器Terra查看了这个案例,它可以完全解决这个问题。 我认为可以通过提供我自己的NSURLProtocol来解决这个问题,但这似乎太复杂了。 我错过了什么?

尝试为需要authentication的所有域使用sharedCredentialStorage。

这是UIWebView的工作示例,它针对仅启用了BasicAuthentication的Windows IIS进行了testing

这是如何添加您的网站凭据:

  NSString* login = @"MYDOMAIN\\myname"; NSURLCredential *credential = [NSURLCredential credentialWithUser:login password:@"mypassword" persistence:NSURLCredentialPersistenceForSession]; NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"myhost" port:80 protocol:@"http" realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge authenticationMethod:NSURLAuthenticationMethodDefault]; [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; [protectionSpace release]; 

您的webView现在应该工作,如果它不工作,使用下一个代码来debugging,尤其是检查didReceiveAuthenticationChallenge的日志消息。

  #import "TheSplitAppDelegate.h" #import "RootViewController.h" @implementation TheSplitAppDelegate @synthesize window = _window; @synthesize splitViewController = _splitViewController; @synthesize rootViewController = _rootViewController; @synthesize detailViewController = _detailViewController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // Add the split view controller's view to the window and display. self.window.rootViewController = self.splitViewController; [self.window makeKeyAndVisible]; NSLog(@"CONNECTION: Add credentials"); NSString* login = @"MYDOMAIN\\myname"; NSURLCredential *credential = [NSURLCredential credentialWithUser:login password:@"mypassword" persistence:NSURLCredentialPersistenceForSession]; NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"myhost" port:80 protocol:@"http" realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge authenticationMethod:NSURLAuthenticationMethodDefault]; [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; [protectionSpace release]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://myhost/index.html"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:12 ]; NSLog(@"CONNECTION: Run request"); [[NSURLConnection alloc] initWithRequest:request delegate:self]; return YES; } - (void)applicationWillResignActive:(UIApplication *)application { } - (void)applicationDidEnterBackground:(UIApplication *)application { } - (void)applicationWillEnterForeground:(UIApplication *)application { } - (void)applicationDidBecomeActive:(UIApplication *)application { } - (void)applicationWillTerminate:(UIApplication *)application { } - (void)dealloc { [_window release]; [_splitViewController release]; [_rootViewController release]; [_detailViewController release]; [super dealloc]; } - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; { NSLog(@"CONNECTION: got auth challange"); NSString* message = [NSString stringWithFormat:@"CONNECTION: cred cout = %i", [[[NSURLCredentialStorage sharedCredentialStorage] allCredentials] count]]; NSLog(message); NSLog([connection description]); NSLog([NSString stringWithFormat:@"CONNECTION: host = %@", [[challenge protectionSpace] host]]); NSLog([NSString stringWithFormat:@"CONNECTION: port = %i", [[challenge protectionSpace] port]]); NSLog([NSString stringWithFormat:@"CONNECTION: protocol = %@", [[challenge protectionSpace] protocol]]); NSLog([NSString stringWithFormat:@"CONNECTION: realm = %@", [[challenge protectionSpace] realm]]); NSLog([NSString stringWithFormat:@"CONNECTION: authenticationMethod = %@", [[challenge protectionSpace] authenticationMethod]]); } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ // release the connection, and the data object [connection release]; // inform the user NSLog(@"CONNECTION: failed! Error - %@ %@", [error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]); } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; { NSLog(@"CONNECTION: received response via nsurlconnection"); } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection; { NSLog(@"CONNECTION: USE!"); return YES; } @end 

WebView身份validation的最终解决scheme基于自定义协议实现。 所有的协议注册为堆栈,所以如果你重新定义HTTP协议,它会拦截来自webView的所有请求,所以你必须检查传入的请求分配的属性,并重新将其包装成新的请求,并通过自己的连接再次发送。 由于你在堆栈中,你的请求会再次来到你的头上,你必须忽略它。 因此,它协议栈下降到真正的HTTP协议实现,因为你的要求没有authentication,你会得到authentication的要求。 authentication后,你会得到一个真正的响应从服务器,所以你重新打包响应,并回复从webView收到的原始请求,就是这样。

不要尝试创build新的请求或响应主体,你只需重新发送它们。 最终的代码大概是30-40行代码,它非常简单,但需要大量的debugging和debugging。

不幸的是,我不能在这里提供代码,因为我已经被分配到不同的项目,我只是想说,我的post是错误的方式,它会在用户更改密码时发生。

使用cocoa的HTTP基本authentication的秘密是知道NSURL和相关的类。

  • NSURL
  • 的NSURLRequest / NSMutableURLRequest
  • NSURLConnection的
  • NSURLCredential
  • NSURLCredentialStorage
  • NSURLProtectionSpace
  • UIWebView / WebView / NIWebController等

真正的魔法来自NSURLConnection。 用devDocs的话来说,“一个NSURLConnection对象提供了支持来执行URL请求的加载”。 如果你想在后台加载一些URL而不显示它,你可以使用NSURLConnection。 NSURLConnection的真正的力量是在方法中

 + (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate 

NSURLConnectionDelegate协议具有用于响应成功连接,致命错误和身份validation挑战的方法。 如果您尝试访问受HTTP基本身份validation保护的数据,则这是Cocoa如何执行的。 在这一点上,应该有一个清晰的例子。

 //basic HTTP authentication NSURL *url = [NSURL URLWithString: urlString]; NSMutableURLRequest *request; request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:12]; [self.webView openRequest:request]; (void)[NSURLConnection connectionWithRequest:request delegate:self]; 

这将创build一个URL。 从URL创buildURLRequest。 URLRequest然后加载到Web视图中。 请求也被用来做一个URLConnection。 我们并没有真正使用连接,但是我们需要接收关于authentication的通知,所以我们设置了委托。 代表只需要两种方法。

 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; { NSURLCredential * cred = [NSURLCredential credentialWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceForSession]; [[NSURLCredentialStorage sharedCredentialStorage]setCredential:cred forProtectionSpace:[challenge protectionSpace]]; } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection; { return YES; } 

无论何时出现身份validation挑战,都会将凭证添加到凭证存储中。 您还可以告知连接使用凭据存储。

我刚刚通过使用UIWebViewNSMutableURLRequest设置基本身份validation凭据来实现这一点。 这也避免了在执行sharedCredentialStorage时发生的往返sharedCredentialStorage (当然也涉及权衡)。

解:

  NSString *url = @"http://www.my-url-which-requires-basic-auth.io" NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, password]; NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; [mutableRequest setValue:authValue forHTTPHeaderField:@"Authorization"]; NSURLRequest *request = [mutableRequest copy]; NSURLRequest *request = [NSURLRequest basicAuthHTTPURLRequestForUrl:url]; [self.webView loadRequest:request]; 

你可以抓住NSData + Base64类别,它实现了Matt Gallagher页面的 NSData的base64EncodedString (当我下载时,它在博客文章的底部)

对于TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm]对于SVWebViewController [https://github.com/samvermette/SVWebViewController]

请务必记住,注销会话和UIWebView凭据并不那么容易。 在这里看到答案: https : //stackoverflow.com/a/18143902/2116338 。