为什么HTTPS NSURLSession连接每个域只挑战一次?

时间:2014-11-21 15:57:28

标签: ios cocoa-touch https nsurlsession

通过HTTPS连接到服务器时,我实施NSURLSessionDelegate方法URLSession:didReceiveChallenge:completionHandler:来实现一些自定义功能。

问题是这个委托方法只在第一次发出请求时被调用(后续请求不会调用此方法)。我的自定义功能要求为每个请求调用委托方法。

以下是一个例子:

- (IBAction)reload:(id)sender {
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];
    // Note that https://www.example.com is not the site I'm really connecting to.
    NSURL *URL = [NSURL URLWithString:@"https://www.example.com"];
    NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];

    [[session dataTaskWithRequest:URLRequest
                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                    // Response received here.
                }] resume];
}

#pragma NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    // Called only for the first request, subsequent requests do no invoke this method.
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

由于我希望URLCredential是每个会话或每个任务,我检查了NSURLCredential我传递给completionHandler,我发现它persistence NSURLCredentialPersistenceForSession }(这是不可变的),这似乎是正确的。

我还检查了[NSURLCredentialStorage allCredentials]并且它是空的,所以它没有缓存那里的凭据。

我注意到,如果我随后向具有不同域的HTTPS URL发出请求,则会针对该域调用一次质询,因此它基于每个域。

那么挑战是如何进行的呢?

修改

切换到NSURLSessionTaskDelegate并使用URLSession:task:didReceiveChallenge:completionHandler:没有任何区别。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

编辑 related question

编辑由于目前似乎无法解决这个问题,我已经提交了一份Apple错误报告:19072802

1 个答案:

答案 0 :(得分:10)

您在此处真正要做的是评估每个请求的服务器凭据的信任度。

Technote 2232: HTTPS Server Trust Evaluation描述了高级别的HTTP信任评估,并详细介绍了如何实现它。

使用SSL / TLS连接主机时,主机会显示一组加密凭据。您的应用程序(可能直接是用户)必须评估这些凭据并确定是否可以信任。

这就像看某人的驾驶执照或护照一样,并决定他们是谁,他们是谁。

想象一下,如果你为他们说的每个单词看一次某人的身份证明。这会得到tedius!除非这个人改变了,或者他们的身份改变了,否则没有意义。如果服务器或其凭据发生更改,iOS将执行信任评估。

这实际上发生在HTTP下面的传输(套接字)层,但基金会感谢在NSURLConnectionNSURLSession等API中将此更高层暴露为给定保护空间的凭证挑战。如果保护空间(主机)或服务器凭据发生更改,则会发生新的凭据质询。这将反过来促进信任评估。

由于SSL / TLS是套接字级别的安全措施,因此实际工作远远低于安全套接字框架SecureTransport内的基础URL加载系统。 SecureTransport维护自己的每进程TLS会话缓存。这是您必须规避的层以获取您正在寻找的行为 - 您需要清除每个连接的TLS会话缓存,或强制SecureTransport忽略您的流程的会话缓存。

Technical Q&A 1727: TLS Session Cache更详细地描述了SecureTransport会话缓存,并且可能提供一些有趣的选项来绕过TLS缓存(即弄乱DNS)。

此时没有用于清除或修改SecureTransport TLS会话缓存的API。您可以提交请求此功能的雷达。

TL; DR;

"那么为什么挑战只进行一次?" 第一个TLS信任评估的结果由SecureTransport缓存在会话缓存中。

此时无法控制该特定行为。

您可以尝试使用其他一些HTTPS库或框架(例如OpenSSL),YMMV。