iOS / Cocoa – NSURLSession – 处理基本的HTTPS授权
[编辑提供更多信息]
(我没有使用AFNetworking来做这个项目,我将来可能会这样做,但是希望先解决这个问题/误会。)
服务器设置
我不能在这里提供真正的服务,但它是一个简单,可靠的服务,根据以下URL返回XML:
https://开头的用户名:password@example.com/webservice
我想通过使用GET连接到通过HTTPS的URL,并确定任何身份validation失败(HTTP状态代码401)。
我已经确认Web服务可用,并且我可以成功(http状态码200)使用指定的用户名和密码从URL抓取XML。 我已经通过Web浏览器和AFNetworking 2.0.3完成了这个工作,并且使用了NSURLConnection。
我也证实,我在所有阶段使用正确的凭证。
鉴于正确的凭据和下面的代码:
// Note: NO delegate provided here. self.sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; self.session = [NSURLSession sessionWithConfiguration:self.sessionConfig delegate:nil delegateQueue:nil]; NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:self.requestURL completionHandler: ...
上面的代码将工作 。 它将成功连接到服务器,获得一个200的http状态码,并返回(XML)数据。
问题1
这种简单的方法在证书无效的情况下失败。 在这种情况下,完成块从不被调用,不提供状态码(401),并且最终任务超时。
试图解决scheme
我将一个委托分配给NSURLSession,并处理以下callback:
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if (_sessionFailureCount == 0) { NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone]; completionHandler(NSURLSessionAuthChallengeUseCredential, cred); } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } _sessionFailureCount++; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if (_taskFailureCount == 0) { NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone]; completionHandler(NSURLSessionAuthChallengeUseCredential, cred); } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } _taskFailureCount++; }
问题1当使用试图解决scheme时
请注意使用ivars _sessionFailureCount和_taskFailureCount。 我正在使用这些,因为挑战对象的@previousFailureCount属性永远不会进步! 它始终保持为零,无论这些callback方法被调用多less次。
问题2当使用试图解决scheme时
尽pipe使用了正确的凭证(通过成功使用nil委托来certificate),authentication失败了。
以下callback发生:
URLSession:didReceiveChallenge:completionHandler: (challenge @ previousFailureCount reports as zero) (_sessionFailureCount reports as zero) (completion handler is called with correct credentials) (there is no challenge @error provided) (there is no challenge @failureResponse provided) URLSession:didReceiveChallenge:completionHandler: (challenge @ previousFailureCount reports as **zero**!!) (_sessionFailureCount reports as one) (completion handler is called with request to cancel challenge) (there is no challenge @error provided) (there is no challenge @failureResponse provided) // Finally, the Data Task's completion handler is then called on us. (the http status code is reported as zero) (the NSError is reported as NSURLErrorDomain Code=-999 "cancelled")
(NSError还提供了一个NSErrorFailingURLKey,它显示了URL和凭证是正确的。)
任何build议欢迎!
您不需要为此实现委托方法,只需在请求上设置授权HTTP头,例如
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://whatever.com"]]; NSString *authStr = @"username:password"; NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding]; NSString *authValue = [NSString stringWithFormat: @"Basic %@",[authData base64EncodedStringWithOptions:0]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; //create the task NSURLSessionDataTask* task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { }];
提示与无提示HTTP身份validation
在我看来,关于NSURLSession和HTTP身份validation的所有文档都会跳过这样一个事实,即可以提示身份validation的要求(如使用.htpassword文件的情况)或自发的 (如处理REST服务时的常见情况)。
对于提示的情况,正确的策略是实现委托方法: URLSession:task:didReceiveChallenge:completionHandler:
对于自发情况,委托方法的实施只会为您提供validationSSL挑战(例如保护空间)的机会。 因此,在处理REST时,您可能需要手动添加authentication标头,如@malhal指出的那样。
这是一个更详细的解决scheme,跳过创build一个NSURLRequest。
// // REST and unprompted HTTP Basic Authentication // // 1 - define credentials as a string with format: // "username:password" // NSString *username = @"USERID"; NSString *password = @"SECRET"; NSString *authString = [NSString stringWithFormat:@"%@:%@", username, secret]; // 2 - convert authString to an NSData instance NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding]; // 3 - build the header string with base64 encoded data NSString *authHeader = [NSString stringWithFormat: @"Basic %@", [authData base64EncodedStringWithOptions:0]]; // 4 - create an NSURLSessionConfiguration instance NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; // 5 - add custom headers, including the Authorization header [sessionConfig setHTTPAdditionalHeaders:@{ @"Accept": @"application/json", @"Authorization": authHeader } ]; // 6 - create an NSURLSession instance NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; // 7 - create an NSURLSessionDataTask instance NSString *urlString = @"https://API.DOMAIN.COM/v1/locations"; NSURL *url = [NSURL URLWithString:urlString]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler: ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { if (error) { // do something with the error return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode == 200) { // success: do something with returned data } else { // failure: do something else on failure NSLog(@"httpResponse code: %@", [NSString stringWithFormat:@"%ld", (unsigned long)httpResponse.statusCode]); NSLog(@"httpResponse head: %@", httpResponse.allHeaderFields); return; } }]; // 8 - resume the task [task resume];
希望这将有助于任何遇到这种logging差异的人。 我终于使用testing代码,一个本地代理ProxyApp,并在我的项目的Info.plist
文件中强制禁用NSAppTransportSecurity
(通过iOS 9 / OSX 10.11上的代理检查SSLstream量所必需的)来解决这个问题。
简答:您所描述的行为与基本的服务器身份validation失败一致。 我知道你已经报告你已经证实它是正确的,但我怀疑服务器上有一些根本的validation问题(不是你的iOS代码)。
很长的回答:
-
如果你没有委托使用
NSURLSession
并在URL中包含用户名/密码,那么如果用户名/密码组合正确,则NSURLSessionDataTask
completionHandler
块将被调用。 但是,如果身份validation失败,NSURLSession
似乎会重复尝试发出请求,每次都使用相同的身份validation凭据,并且completionHandler
似乎不会被调用。 (我注意到通过查看与Charles Proxy的连接)。这不会使我非常谨慎的
NSURLSession
,但是再次无NSURLSession
演绎实际上做不到这一点。 使用身份validation时,使用基于delegate
的方法似乎更加健壮。 -
如果在创build数据任务时使用带有指定
delegate
的NSURLSession
(并且在创build数据任务时没有completionHandler
参数),则可以检查NSURLSession
错误的性质,即检查challenge.error
和challenge.failureResponse
对象。 你可能想用这些结果来更新你的问题。_failureCount
一下,你似乎正在维护自己的_failureCount
计数器,但是你可以利用challenge.previousFailureCount
属性。 -
也许你可以分享一些有关你的服务器使用的authentication性质的细节。 我只问,因为当我在我的Web服务器上保护目录时,它不调用
NSURLSessionDelegate
方法:- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
但是,它调用
NSURLSessionTaskDelegate
方法:- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
就像我所说的,你描述的行为是由服务器上的authentication失败组成的。 在服务器上共享有关身份validation设置性质的细节以及NSURLAuthenticationChallenge
对象的详细信息可能有助于我们诊断发生了什么事情。 您可能还想要在Web浏览器中input带有用户名/密码的URL,并且还可以确认是否存在基本身份validation问题。
- 带有完成处理程序的NSURLConnection sendAsynchronousRequestvalidation
- 暴露Rails /deviseauthentication到iOS应用程序
- 将个人资料图片设置为Firebase用户的最佳方法是什么?
- 如何在离线时将数据写入Firebase? Swift3
- 被拒绝的应用程序:17.2:需要用户共享个人信息的应用程序
- iOS5:willSendRequestForAuthenticationChallenge方法正在recursion运行
- AWSWebIdentityCredentialsProvider如何获取参数值?
- parsingSwiftlogin错误:无效的用户凭据
- 如何让Rails告诉用户login的移动应用程序?