NSURLSession生命周期和基本授权 [英] NSURLSession lifecycle and basic authorization

查看:101
本文介绍了NSURLSession生命周期和基本授权的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I can't consistently read response from a server if I use code below.

标题:

#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调用(例如按钮操作下的位置代码)

Can be called from any VC (place code under button action for example)

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

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

If you start program and call this code - it will show in NSLog completion with 401. Second try - will not work. Or might work if wait a little. But it won't send server requests as you push button.

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

NSURLSession somehow "remembers" failed attempts and won't return anything? Why is this behavior? I want to see 2 NSLog messages every time I push button.

推荐答案

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

TL;DR; You are not handling authentication correctly in your example.

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

This is what happens when an iOS or MacOS client encounters a URL that requires authentication:


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

该请求的服务器响应的状态代码为401,并包含WWW-Authenticate标头。这告诉客户端这是受保护的资源,并指定用于访问资源的身份验证方法。在iOS和MacOS中,这是委托响应的身份验证挑战。 WWW-Authenticate标题在文档中特别提到,以突出显示这一点

The server response for that request has a status code of 401 and includes the WWW-Authenticate header. This tells the client this is a protected resource, and specifies what authentication method to use to access the resource. In iOS and MacOS, this is the "authentication challenge" that a delegate responds to. The WWW-Authenticate header is specifically mentioned in the documentation to highlight this.


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

  • Normally on iOS and MacOS, if a delegate is not provided or does not handle authentication challenges, the URL loading system will try to find an appropriate credential for this resource and authentication type by looking in NSURLCredentialStorage. It looks for a matching credential that has been saved as the default.

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

If a delegate implementing authentication IS provided, it's up to that delegate to provide a credential for that resource.

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

When the system has a credential for the authentication challenge the request is attempted again with the credential.

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

GET www.example.com/protected Authorization: Basic blablahaala

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

This explains the behavior you see in Charles, and is the correct behavior according to the various HTTP specifications.

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

Obviously, if you do not want to implement a delegate for your connection you have the option of putting a credential for the resource you are accessing in NSURLCredentialStorage. The system will use this, and will not require you to implement a delegate for the credential.

创建NSURLCredential:

Create an NSURLCredential:

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

NSURLCredentialPersistencePermanent 将告诉 NSURLCredentialStorage 将其永久存储在钥匙串中。您可以使用其他可能的值,例如 NSURLCredentialPersistenceForSession 这些内容已在文档中介绍。。您应该避免使用 NSURLCredentialPersistencePermanent 使用尚未验证的凭据,使用会话或无,直到验证凭据。您可能已经看到使用KeychainWrapper或直接访问Keychain API来保存互联网用户名和密码的项目 - 这不是首选方法, NSURLCredentialStorage 是。

NSURLCredentialPersistencePermanent will tell NSURLCredentialStorage to store this permanently in the keychain. There are other possible values you can use, such as NSURLCredentialPersistenceForSession. These are covered in the documentation. . You should avoid using NSURLCredentialPersistencePermanent with credentials that have not been validated, use session or none until the credential has been validated. You have probably seen projects using 'KeychainWrapper' or directly accessing the Keychain API to save internet usernames and passwords - this is not the preferred way to do this, NSURLCredentialStorage is.

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

Create an NSURLProtectionSpace with the correct host, port, protocol, realm, and authentication method:

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

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

Note that [[url port] integerValue] will not provide defaults for HTTP (80) or HTTPS (443). You must provide those. The realm MUST match what the server provides.

最后,将其放入 NSURLCredentialStorage

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

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

This will allow the URL loading system to use this credential from this point forward. Essentially the same process can also be used for a SSL/TLS server trust reference as well.

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

In your question you are handling server trust, but not the NSURLAuthenticationMethodHTTPBasic . When your application receives an authentication challenge for the HTTP Basic Auth, you are not responding to it, and things go downhill from there. In your case, you probably do not need to implement URLSession:didReceiveChallenge:completionHandler: at all if you do the steps above to set the default basic authentication credential for this protection space. The system will handle the NSURLAuthenticationMethodServerTrust by performing the default trust evaluation. The system will then find the default credential you set for this protection space for basic authentication and use that.

更新

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

Based on new information in the comments, and running a modified version of the code, OP is actually getting this error in response to his request: NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) This error can be found in SecureTransport.h. The root certificate on the server credentials doesn't exist or is not trusted by the system. This is very rare, but can happen. Technote 2232 explains how to customize the server trust evaluation in the client to allow this certificate.

这篇关于NSURLSession生命周期和基本授权的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆