【问题标题】:How to do authentication in UIWebView properly?如何正确在 UIWebView 中进行身份验证?
【发布时间】:2012-01-25 08:37:41
【问题描述】:

我想在我的 UIWebView 中支持 HTTP 基本身份验证。

目前,我正在取消

中的请求

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

我的问题是,从不为嵌入元素(如图像、JavaScript 或 CSS 文件)调用委托。因此,如果我的 HTML 页面引用了受基本身份验证保护的图像,则无法正确加载该图像。此外,永远不会调用 webView:didFinishLoad:,因为 Web 视图无法完全加载页面。

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

【问题讨论】:

  • 嘿 NeoNacho,你知道如何解决这个问题吗?我有同样的问题 - 我可以加载 html 页面,但所有的 css / javascript 永远不会被正确加载/处理。如果您有任何提示,请分享:)

标签: ios authentication uiwebview


【解决方案1】:

尝试对您需要进行身份验证的所有域使用 sharedCredentialStorage。

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

这是添加站点凭据的方法:

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

编辑:Swift 4 中的相同代码

let login = "MYDOMAIN\\myname"
let credential = URLCredential(user:login, password:"mypassword", persistence:.forSession)
let protectionSpace = URLProtectionSpace(host:"myhost", port:80, protocol:"http", realm:"myhost", authenticationMethod:NSURLAuthenticationMethodDefault)
URLCredentialStorage.shared.setDefaultCredential(credential, for:protectionSpace)

您的 webView 现在应该可以工作了,如果它不工作,请使用下一个代码进行调试,特别是检查 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 身份验证的最终解决方案是基于自定义协议实现。所有协议都注册为堆栈,因此如果您重新定义 HTTP 协议,它将拦截来自 webView 的所有请求,因此您必须检查与传入请求相关的属性并将其重新打包到新请求中并通过您自己的连接再次发送。由于您在堆栈中,您的请求会立即再次出现,您必须忽略它。因此它将协议栈向下传递到真正的 HTTP 协议实现,因为您的请求未经过身份验证,您将获得身份验证请求。在authenticaiton 之后,你会从服务器得到一个真实的响应,所以你重新打包响应并回复从webView 收到的原始请求,就是这样。

不要尝试创建新的请求或响应正文,您必须重新发送它们。最终的代码大约有 30-40 行代码,非常简单,但需要大量调试和测试。

不幸的是我不能在这里提供代码,因为我已经分配到不同的项目,我只想说我的帖子是错误的方式,当用户更改密码时它卡住了。

【讨论】:

  • 这会自动发生吗?对主机进行身份验证后,似乎无需对后续请求重新进行身份验证。
  • 我看到了与 Max 相同的行为。一旦通过身份验证,我就不会再被要求重新进行身份验证。我猜我的 iOS 模拟器会话中存储了一些东西?我尝试重置模拟器,但仍然没有受到挑战。奇怪。
  • "版主删除了对我很重要的帖子,我删除了对他很重要的帖子。这篇文章花了我 4 周的时间研究。祝您有美好的一天"。很遗憾地向您宣布,您的原始答案已存储在帖子历史记录中,现在将被检索。感谢您的关注。
【解决方案2】:

使用 cocoa 进行 HTTP 基本身份验证的秘诀是了解 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 协议具有响应成功连接、致命错误和身份验证挑战的方法。如果您尝试访问受 HTTP 基本身份验证保护的数据,这就是 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];

这会创建一个 URL。从 URL 创建一个 URLRequest。然后将 URLRequest 加载到 Web 视图中。 Request 还用于创建 URLConnection。我们并没有真正使用连接,但我们需要接收有关身份验证的通知,因此我们设置了委托。我们只需要委托的两个方法。

- (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;
}

只要有身份验证质询,就会将凭据添加到凭据存储中。您还告诉连接使用凭证存储。

【讨论】:

  • 只是一个问题:当您收到 didReceiveAuthenticationChallenge 事件时,我看到您正在 sharedCredentialStorage 中添加凭据。但是,每次收到此事件时重置凭据是否是一种好习惯?只是问一下,我想弄清楚 sharedCredentialStorage 是如何工作的。
  • 这可能不是最好的解决方案,但它很实用,我再也没有回去清理它。如果密码不会更改,那么您可以将其放在委托调用之外并且只执行一次,如果凭据将更改,则在收到质询时查找当前用户名密码可能会很好。
【解决方案3】:

我刚刚通过使用NSMutableURLRequestUIWebView 设置基本身份验证凭据来实现这一点。这也避免了在实现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];

您可以从 Matt Gallagher's page 获取实现 base64EncodedString 的 NSData 的 NSData+Base64 类别(我下载它时它在博客文章的底部)

【讨论】:

  • 只是想补充一下,您不再需要 NSData+Base64 类别,因为该功能现在包含在标准 NSData 实现中(虽然方法签名略有不同)
  • 确实 - 查看 Apple 文档以了解 Base64 编码 here
  • 最后你定义了一个新的 url 请求 [NSURLRequest basicAuthHTTPURLRequestForUrl:url]; 之前的定义如何影响那个请求?
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    请务必记住,使用会话和 UIWebView 凭据注销并不是那么容易。在此处查看答案:https://stackoverflow.com/a/18143902/2116338

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-17
      • 1970-01-01
      • 1970-01-01
      • 2012-12-30
      • 2012-04-15
      • 1970-01-01
      相关资源
      最近更新 更多