ios 如何在 UIWebView 中正确进行身份验证?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8999776/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to do authentication in UIWebView properly?
提问by NeoNacho
I would like to support HTTP Basic Authentication in my UIWebView.
我想在我的 UIWebView 中支持 HTTP 基本身份验证。
At the moment, I am canceling requests in
目前,我正在取消请求
webView:shouldStartLoadWithRequest:navigationType:
then handle them in my own NSURLConnectionDelegate to check for and provide credentials if needed. I then use loadData:MIMEType:textEncodingName:baseURL:
to present HTML in the web view. That works fine for any URLs that are passed to the delegate.
webView:shouldStartLoadWithRequest:navigationType:
然后在我自己的 NSURLConnectionDelegate 中处理它们以检查并在需要时提供凭据。然后我使用loadData:MIMEType:textEncodingName:baseURL:
在 web 视图中呈现 HTML。这适用于传递给委托的任何 URL。
My problem is that the delegate is never called for embedded elements, like images, JavaScript or CSS files. So if I have an HTML page which references an image which is protected with basic authentication, that image cannot be loaded properly. Additionally, webView:didFinishLoad:
is never called, because the web view could not fully load the page.
我的问题是从不为嵌入元素调用委托,例如图像、JavaScript 或 CSS 文件。因此,如果我有一个 HTML 页面引用受基本身份验证保护的图像,则无法正确加载该图像。此外,webView:didFinishLoad:
永远不会调用,因为 Web 视图无法完全加载页面。
I have checked that case with Terra, a third-party browser available on the App Store, and it can fully cope with that situation. I think it would be possible to solve this by providing my own NSURLProtocol, but that seems too complicated. What am I missing?
我已经使用 App Store 上提供的第三方浏览器 Terra 检查了这种情况,它完全可以应对这种情况。我认为可以通过提供我自己的 NSURLProtocol 来解决这个问题,但这似乎太复杂了。我错过了什么?
回答by Basic Primitives Support
Try to use sharedCredentialStorage for all domains you need to authenticate.
尝试将 sharedCredentialStorage 用于您需要进行身份验证的所有域。
Here is working sample for UIWebView it was tested against Windows IIS having only BasicAuthentication enabled
这是 UIWebView 的工作示例,它针对仅启用 BasicAuthentication 的 Windows IIS 进行了测试
This is how to add your site 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];
Edit: same code in Swift 4
编辑: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)
Your webView is supposed to work now, if it does not work use next code to debug, especially check log messages of didReceiveAuthenticationChallenge.
您的 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
The final solution for WebView authentication was based on custom protocol implementation. All protocols registered as a stack, so if you redefine HTTP protocol it would intercept all requests coming from webView, so you have to check attributes assotiated with incoming request and repack it into new request and send it again via your own connection. Since you are in stack, your request immidiatly comes to you again and you have to ignore it. So it goes down protocol stack to real HTTP protocol implementation, since your request is not athenticated you'll get authenticaiton request. And after authenticaiton you'll get a real response from server, so you repack response and reply to original request received from webView and that's it.
WebView 身份验证的最终解决方案基于自定义协议实现。所有协议都注册为堆栈,因此如果您重新定义 HTTP 协议,它将拦截来自 webView 的所有请求,因此您必须检查与传入请求相关的属性并将其重新打包为新请求并通过您自己的连接再次发送。由于您在堆栈中,您的请求会立即再次出现,您必须忽略它。所以它会沿着协议栈到真正的 HTTP 协议实现,因为你的请求没有经过身份验证,你会得到身份验证请求。在身份验证之后,您将获得来自服务器的真实响应,因此您重新打包响应并回复从 webView 收到的原始请求,仅此而已。
Don;t try to create new requests or responses bodies, you have to just resend them. The final code would be aproximetly 30-40 lines of code and it is quite simple, but requires a lot of debuging and tetsing.
不要尝试创建新的请求或响应主体,您必须重新发送它们。最终的代码大约有 30-40 行代码,非常简单,但需要大量调试和测试。
Unfortunatlly I cannot provide code here, since I am assigned to different project already, I just wanted to say that my post is wrong way, it stucks when user changes password.
不幸的是我不能在这里提供代码,因为我已经被分配到不同的项目,我只想说我的帖子是错误的,当用户更改密码时它会卡住。
回答by Andrew Hoos
The secret to HTTP basic authentication using cocoa is knowing NSURL and the related classes.
使用可可进行 HTTP 基本身份验证的秘诀是了解 NSURL 和相关类。
- NSURL
- NSURLRequest/NSMutableURLRequest
- NSURLConnection
- NSURLCredential
- NSURLCredentialStorage
- NSURLProtectionSpace
- UIWebView/WebView/NIWebController etc.
- 网址
- NSURLRequest/NSMutableURLRequest
- NSURL连接
- NSURLCredential
- NSURLCredentialStorage
- NSURL保护空间
- UIWebView/WebView/NIWebController 等
The real magic comes from NSURLConnection. In the words of the devDocs, "An NSURLConnection object provides support to perform the loading of a URL request." If you want to load some a URL in the background without displaying it you would use NSURLConnection. The real power of the NSURLConnection is in the method
真正的魔法来自 NSURLConnection。用 devDocs 的话来说,“NSURLConnection 对象提供了执行 URL 请求加载的支持。” 如果你想在后台加载一些 URL 而不显示它,你可以使用 NSURLConnection。NSURLConnection 的真正力量在于方法
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate
The NSURLConnectionDelegate protocol has methods for responding to successful connections, fatal errors, and authentication challenges. If you are trying to access data Protected by HTTP basic authentication this is how Cocoa does it. At this point an example should bring some clarity.
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];
This creates a URL. From the URL a URLRequest is created. The URLRequest is then loaded in the web view. The Request is also used to make a URLConnection. We don't really use the connection, but we need to receive notifications about authentication so we set the delegate. There are only two methods we need from the delegate.
这将创建一个 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;
}
Whenever there is an authentication challenge a credential is added to the credential storage. You also tell the connection to use the credential storage.
每当出现身份验证质询时,都会将凭据添加到凭据存储中。您还告诉连接使用凭证存储。
回答by NSTJ
I've just implemented this by setting basic auth credentials using an NSMutableURLRequest
for the UIWebView
. This also avoids the round trip incurred when implementing sharedCredentialStorage
(of course there are tradeoffs involved).
我只是通过设置使用基本身份验证凭据来实现此NSMutableURLRequest
为UIWebView
。这也避免了在实现时发生的往返sharedCredentialStorage
(当然需要权衡)。
Solution:
解决方案:
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];
You can grab the NSData+Base64 category which implements the base64EncodedString
for NSData from Matt Gallagher's page(it was at the bottom of the blog post when I downloaded it)
您可以base64EncodedString
从Matt Gallagher 的页面获取实现 NSData 的 NSData+Base64 类别(当我下载它时,它位于博客文章的底部)
回答by Hla Min Swe
For TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm] For SVWebViewController [https://github.com/samvermette/SVWebViewController]
对于 TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm] 对于 SVWebViewController [https://github.com/samvermette/SVWebViewController]
回答by mrplants
Make sure to remember that logging out is not so easy with sessions and UIWebView credentials. See answer here: https://stackoverflow.com/a/18143902/2116338.
请务必记住,使用会话和 UIWebView 凭据注销并不容易。请参阅此处的答案:https: //stackoverflow.com/a/18143902/2116338。