NSURLRequest对象经常会遇到身份认证的询问,或者是向已连接的服务器请求数据时需要证书。NSURLSession类会在请求中遇到身份验证时通知它的代理,以便我们来采取相应的措施。

⏰ ⏰ ⏰ 重要

除非服务器的响应(response)首部中包含了WWW-Authenticate字段,否则URL Loading System的类是不会去调用它们的代理去处理请求相关的询问。其他的身份认证类型,比如代理(服务器)身份认证和TLS信任验证则不需要该首部字段。

确定如何响应身份认证询问

如果一个session的任务要求进行身份认证,但是此时并没有可用的有效证书,不管是作为请求的URL或者是NSURLCredentialStorage共享的一部分,它(任务)都会创建一个身份认证的询问。首先会发送给任务代理URLSession:task:didReceiveChallenge:completionHandler: 处理身份认证的询问。如果任务代理并没有响应该消息,便发送给session的代理URLSession:didReceiveChallenge:completionHandler:来处理身份认证的询问。

总结一下

对于身份认证的处理是:先给任务的代理处理。如果任务的代理不处理,则递交给sesssion的代理来处理。

为了能够让网络连接正常(不因为身份认证相关的事务断开)。代理可以做以下3个选项:

  • 提供身份认证证书;
  • 在没有证书的情况下继续传输;
  • 取消身份认证;

传递给相关方法的NSURLAuthenticationChallenge实例包含了触发身份认证的询问,为了能够让代理确定正确的做法(上诉三种选项),对询问进行了很多种尝试,包括已有的证书,需要证书的NSURLProtectionSpace和询问的发送者。

如果身份认证的询问已尝试了先前的认证方式但是失败了(例如,用户在服务器上修改了他的密码),我们可以通过调用身份认证询问的proposedCredential方法来尝试获得证书(这句感觉没有翻译顺畅,原文是:you can obtain the attempted credentials by calling proposedCredential on the authentication challenge,原文地址)。然后,代理对象可以以对话框的方式把这些证书呈现给用户。

在身份认证询问失败时调用previousFailureCount会返回之前尝试身份认证失败的次数,包括那些不同协议的身份认证。代理可以把这些信息提供给我们,以便我们来确定先前的证书是否失败了,或者是告诉我们身份认证可以尝试的最大次数。

响应身份认证的询问

有三种方法去响应URLSession:didReceiveChallenge:completionHandler:或者URLSession:task:didReceiveChallenge:completionHandler:代理方法。

提供证书

为了尝试身份认证,我们在程序中应该创建一个NSURLCredential对象,其中包含了服务器预计要得到的身份认证信息。我们可以在身份认证询问的保护区域中调用authenticationMethod方法来确定服务器的身份认证方法。一些支持NSURLCredential的身份认证方法是:

  • HTTP基本的身份认证(NSURLAuthenticationMethodHTTPBasic)是用户名和密码。通过NSURLCredential调用其类方法credentialWithUser:password:persistence:来促使用户提供必须的信息。
  • HTTP摘要认证(NSURLAuthenticationMethodHTTPDigest,摘要是自动产生的),和基本认证类似,需要用户的用户名和密码。同样是使用NSURLCredential调用其类方法credentialWithUser:password:persistence:来让用户提供必须的信息。
  • 客户端证书认证(NSURLAuthenticationMethodClientCertificate)需要提供系统身份和服务器身份认证的所有证书。调用NSURLCredential的类方法credentialWithIdentity:certificates:persistence:来创建所需对象。
  • 服务器信任认证(NSURLAuthenticationMethodServerTrust)要求身份认证询问的保护区域提供一个可信的认证信息。使用NSURLCredential的类方法credentialForTrust:创建相应的对象。

在成功创建NSURLCredential对象之后,使用提供的completionHandler的block把该对象传递给身份认证询问的发送者。

没有证书的情况下继续传输

如果代理选择不为身份认证的询问提供证书,而尝试继续通信。把以下的值传递给提供的completionHandler:

断开连接

通过给提供的completionHandler传递NSURLSessionAuthChallengeCancelAuthenticationChallenge参数,代理对象也可选择去取消身份认证的询问。

身份认证样例

下面展示了通过由应用程序偏好设置提供的用户名和密码创建NSURLCredential实例,如果在之前的身份认证失败了,它会取消用户身份认证询问的相关信息。

/// Objective-C
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if ([challenge previousFailureCount] == 0) {
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
                                                                    password:[self preferencesPassword]
                                                                 persistence:NSURLCredentialPersistenceNone];
        completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
    } else {
        // Inform the user that the user name and password are incorrect
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}
/// Swift
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    let proposedCredential = URLCredential(user: self.preferencesName, password: self.preferencesPassword, persistence: .none)
    completionHandler(.useCredential, proposedCredential)
}
If the authentication challenges goes unhandled by the session or task delegate, and credentials are not available or if they fail to authenticate, a continueWithoutCredentialForAuthenticationChallenge: message is sent by the underlying implementation.

执行自定义TLS链验证

在NSURL的API簇里,TLS链验证由程序身份认证的代理方法来处理。不再是向服务器提供身份认证的证书,取而代之的是由应用程序来检查服务器在TLS握手期间提供的证书,然后告诉URL Loading System是接受还是拒绝这些证书。

如果是需要以非标准的方式来执行链验证(例如接受特定的自签名证书进行测试),我们必须要实现URLSession:didReceiveChallenge:completionHandler:或者 URLSession:task:didReceiveChallenge:completionHandler:代理方法中的一个。如果两个代理方法都实现了,相关认证工作会在会话层处理。

在我们的身份认证代理方法中,应该去检查一下在“询问”(这里不是动词,是名词)保护区中身份认证的类型是否是NSURLAuthenticationMethodServerTrust,如果是就从保护区中获取serverTrust信息。

results matching ""

    No results matching ""