[编辑以提供更多信息]
(我没有在这个项目中使用AFNetworking。我将来可能会这样做,但希望先解决这个问题/误解。)
服务器设置
我无法在此提供真实服务,但它是一种简单,可靠的服务,可根据以下URL返回XML:
https://username:password@example.com/webservice
我想使用GET通过HTTPS连接到URL,并确定任何身份验证失败(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: ...
以上代码可以使用。它将成功连接到服务器,获取http状态代码200,并返回(XML)数据。
问题1
如果凭据无效,这种简单方法就会失败。在这种情况下,永远不会调用完成块,不提供状态代码(401),最终任务超时。
擅自解决方案
我为NSURLSession分配了一个委托,并处理了以下回调:
-(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使用经过解决的解决方案
请注意使用ivars _sessionFailureCount和_taskFailureCount。我正在使用这些因为挑战对象的@previousFailureCount属性永远不会提升!无论调用这些回调方法多少次,它始终保持为零。
问题2使用经过解决的解决方案
尽管使用了正确的凭据(正如他们成功使用nil委托所证明的那样),但身份验证失败了。
发生以下回调:
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和凭据是正确的。)
欢迎任何建议!
答案 0 :(得分:29)
您不需要为此实现委托方法,只需在请求上设置授权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) {
}];
答案 1 :(得分:21)
在我看来,关于NSURLSession和HTTP身份验证的所有文档都跳过了以下事实:身份验证要求可以提示(使用.htpassword文件的情况)或 unconmpted (处理REST服务的常见情况)。
对于提示案例,正确的策略是实现委托方法:
URLSession:task:didReceiveChallenge:completionHandler:
;对于自发的情况,委托方法的实现只会为您提供验证SSL质询的机会(例如保护空间)。因此,在处理REST时,您可能需要手动添加身份验证标头,如@malhal指出的那样。
这是一个更详细的解决方案,可以跳过创建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];
希望这可以帮助任何遇到这种记录不良差异的人。我终于使用测试代码,本地代理ProxyApp并在我的项目的NSAppTransportSecurity
文件中强制禁用Info.plist
(在iOS 9上通过代理检查SSL流量所必需的) / OSX 10.11)。
答案 2 :(得分:10)
简答:您描述的行为与基本服务器身份验证失败一致。我知道您已经报告过您已经确认它是正确的,但我怀疑服务器上存在一些基本的验证问题(而不是您的iOS代码)。
答案很长:
如果您在没有代理的情况下使用NSURLSession
并在网址中包含用户ID /密码,那么如果用户ID /密码组合将调用completionHandler
的{{1}}块是正确的。但是,如果身份验证失败,NSURLSessionDataTask
似乎每次都使用相同的身份验证凭据重复尝试发出请求,并且NSURLSession
似乎没有被调用。 (我注意到通过观察与Charles Proxy的连接)。
这对completionHandler
来说并不是一种谨慎的态度,但是再一次,无委托的再现实际上并不能做到这一点。使用身份验证时,使用基于NSURLSession
的方法似乎更加强大。
如果您在创建数据任务时使用delegate
指定NSURLSession
(并且没有delegate
参数),则可以在{中检查错误的性质{1}},即检查completionHandler
和didReceiveChallenge
个对象。您可能希望使用这些结果更新您的问题。
顺便说一下,您似乎维护着自己的challenge.error
计数器,但您可以利用challenge.failureResponse
财产来代替。
也许您可以分享一些有关服务器使用的身份验证性质的详细信息。我只是问,因为当我在我的网络服务器上保护目录时,它不会调用_failureCount
方法:
challenge.previousFailureCount
相反,它调用NSURLSessionDelegate
方法:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
就像我说的那样,您描述的行为包括服务器上的身份验证失败。在服务器上共享有关身份验证设置性质的详细信息以及NSURLSessionTaskDelegate
对象的详细信息可能有助于我们诊断正在进行的操作。您可能还希望在Web浏览器中使用用户ID /密码键入URL,这可能还会确认是否存在基本身份验证问题。