NSURLSession生命周期和基本授权

时间:2014-07-21 17:19:00

标签: ios nsurlsession

如果我使用下面的代码,我无法始终从服务器读取响应。

部首:

#import <Foundation/Foundation.h>
@interface TestHttpClient : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>

-(void)POST:(NSString*) relativePath payLoad:(NSData*)payLoad;

@end

实现:

#import "TestHttpClient.h"

@implementation TestHttpClient

-(void)POST:(NSString*)relativePath payLoad:(NSData*)payLoad
{
    NSURL* url = [NSURL URLWithString:@"http://apps01.ditat.net/mobile/batch"];

    // Set URL credentials and save to storage
    NSURLCredential *credential = [NSURLCredential credentialWithUser:@"BadUser" password:@"BadPassword" persistence: NSURLCredentialPersistencePermanent];
    NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:443 protocol:[url scheme] realm:@"Ditat mobile services endpoint" authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
    [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];

    // Configure session
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    sessionConfig.timeoutIntervalForRequest = 30.0;
    sessionConfig.timeoutIntervalForResource = 60.0;
    sessionConfig.HTTPMaximumConnectionsPerHost = 1;
    sessionConfig.URLCredentialStorage = [NSURLCredentialStorage sharedCredentialStorage]; // Should this line be here??

    NSURLSession *session =     [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    // Create request object with parameters
    NSMutableURLRequest *request =
    [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0];

    // Set header data
    [request setHTTPMethod:@"POST"];
    [request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"Version 1.0" forHTTPHeaderField:@"User-Agent"];
    [request setValue:@"Demo" forHTTPHeaderField:@"AccountId"];
    [request setValue:@"1234-5678" forHTTPHeaderField:@"DeviceSerialNumber"];
    [request setValue:@"iOS 7.1" forHTTPHeaderField:@"OSVersion"];
    [request setHTTPBody:payLoad];

    // Call session to post data to server??
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
    [downloadTask resume];
}

-(void)invokeDelegateWithResponse:(NSHTTPURLResponse *)response fileLocation:(NSURL*)location
{
    NSLog(@"HttpClient.invokeDelegateWithResponse - code %ld", (long)[response statusCode]);
}

#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"NSURLSessionDownloadDelegate.didFinishDownloadingToURL");
    [self invokeDelegateWithResponse:(NSHTTPURLResponse*)[downloadTask response] fileLocation:location];
    [session invalidateAndCancel];
}

// Implemented as blank to avoid compiler warning
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}

// Implemented as blank to avoid compiler warning
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
}

可以从任何VC调用(例如按钮操作下的位置代码)

-(IBAction)buttonTouchUp:(UIButton *)sender
{
    TestHttpClient *client = [[TestHttpClient alloc] init];
    [client POST:@"" payLoad:nil];
    return;
}

如果您启动程序并调用此代码 - 它将在NSLog完成时显示401.第二次尝试 - 将无效。或者等一下可能会有效。但是当你按下按钮时它不会发送服务器请求。

NSURLSession以某种方式“记住”尝试失败并且不会返回任何内容?为什么会这样?我希望每次按下按钮时都能看到2条NSLog消息。

1 个答案:

答案 0 :(得分:26)

TL; DR; 您未在示例中正确处理身份验证。

当iOS或MacOS客户端遇到需要身份验证的URL时会发生这种情况:

  1. 客户端从服务器请求资源 GET www.example.com/protected

  2. 该请求的服务器响应的状态代码为401,并包含WWW-Authenticate标头。这告诉客户端这是受保护的资源,并指定用于访问资源的身份验证方法。在iOS和MacOS中,这是&#34;身份验证挑战&#34;代表回应。 The WWW-Authenticate header is specifically mentioned in the documentation to highlight this

    • 通常在iOS和MacOS上,如果未提供代理或未处理身份验证质询,则URL加载系统将尝试通过查看NSURLCredentialStorage来查找此资源和身份验证类型的相应凭据。它会查找已保存为默认值的匹配凭据。

    • 如果提供了实施身份验证的代理人,则由该代理人提供该资源的凭据。

  3. 当系统具有身份验证质询的凭据时,将使用凭据再次尝试请求。

    获取www.example.com/protected 授权:基本blablahaala

  4. 这解释了您在Charles中看到的行为,并且根据各种HTTP规范是正确的行为。

    显然,如果您不想为您的连接实现委托,您可以选择为您在NSURLCredentialStorage中访问的资源提供凭据。系统将使用此功能,并且不需要您为凭证实现委托。

    创建NSURLCredential:

    credential = [NSURLCredential credentialWithUser:@"some user" password:@"clever password" persistence: NSURLCredentialPersistencePermanent];
    

    NSURLCredentialPersistencePermanent会告诉NSURLCredentialStorage永久存储在钥匙串中。您可以使用其他可能的值,例如NSURLCredentialPersistenceForSessionThese are covered in the documentation.。在验证凭据之前,应避免将NSURLCredentialPersistencePermanent与未经验证的凭据一起使用,使用会话或无。您可能已经看过使用&#39; KeychainWrapper&#39;或直接访问Keychain API以保存互联网用户名和密码 - 这不是首选的方法,NSURLCredentialStorage是。

    使用正确的主机,端口,协议,领域和身份验证方法创建NSURLProtectionSpace

    protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:443 protocol:[url scheme] realm:@"Protected Area" authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
    

    请注意,[[url port] integerValue]不会为HTTP(80)或HTTPS(443)提供默认值。你必须提供这些。领域必须与服务器提供的内容相匹配。

    最后,将其放入NSURLCredentialStorage

    [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
    

    这将允许URL加载系统从此时开始使用此凭据。基本上相同的过程也可以用于SSL / TLS服务器信任引用。

    在您的问题中,您正在处理服务器信任,但不是NSURLAuthenticationMethodHTTPBasic当您的应用程序收到HTTP基本身份验证的身份验证质询时,您没有对此做出响应,而且事情从那里开始走下坡路。在您的情况下,您可能不需要实施URLSession:didReceiveChallenge:completionHandler:完全按照上述步骤设置此保护空间的默认基本身份验证凭据。系统将通过执行默认信任评估来处理NSURLAuthenticationMethodServerTrust。然后,系统将找到您为此保护空间设置的默认凭据,以进行基本身份验证并使用该凭据。

    <强>更新

    基于评论中的新信息并运行代码的修改版本,OP实际上是在响应他的请求时收到此错误: NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 可以在SecureTransport.h中找到此错误。服务器凭据上的根证书不存在或不受系统信任。这种情况非常罕见,但可能会发生。 Technote 2232解释了如何在客户端中将服务器信任评估自定义为allow this certificate