NSURLRequest对象经常遇到验证挑战,或者从其他连接的服务器请求证书。当请求遇到验证挑战的时候,NSURLSession类会通知它的委托,以便它们能够相应的执行。
重要:URL加载系统的类一般不调用它们的委托来处理请求挑战,除非服务器的响应包含WWW-Authenticate头部。其他的验证类型,例如代理验证和TLS信任验证,不需要此头部。
决定如何响应一个验证挑战
如果会话任务请求验证,并且没有有效的证书可用,作为请求URL的一部分或者共享的NSURLCredentialStorage,它会创建一个验证挑战。它首先发送URLSession:task:didReceiveChallenge:completionHandler: 到它的任务委托来处理该验证挑战。如果任务委托没有响应这个消息,该任务会发送URLSession:task:didReceiveChallenge:completionHandler:给它的会话委托来处理这个认证挑战。
为了继续连接,委托由三个选项:
- 提供验证证书。
- 尝试无证书连接。
- 取消验证挑战。
为了帮助确定正确的操作过程,传递给该方法的NSURLAuthenticationChallenge实例包含关于触发该验证挑战的信息、对挑战进行的尝试次数、任何之前尝试的证书、请求证书的NSURLProtectionSpace、以及挑战的发送者。
如果验证挑战之前已经有验证但验证失败(例如,如果用户改变了密码或服务器),你可以通过调用验证挑战中的hproposedCredential来获取尝试的证书。然后该委托把这些证书通过弹出对话框显示给用户。
调用验证挑战的previousFailureCount,返回之前验证尝试的总次数,包括了不同验证协议的尝试。该委托可以抱这些信息提供给用户,已确定之前提供的证书是否失败,或者限制验证尝试次数。
响应验证挑战
以下三种方法是对URLSession:didReceiveChallenge:completionHandler: 或 URLSession:task:didReceiveChallenge:completionHandler:委托方法的响应。
提供证书
要想尝试验证,应用程序应该使用验证信息创建NSURLCredential对象,该验证信息应该符合服务器的要求。你可以通过在提供验证挑战的保护空间中调用authenticationMethod确定服务器的验证方法。NSURLCredential支持一些验证方法:
- HTTP基础验证(NSURLAuthenticationMethodHTTPBasic)需要用户名和密码。提示用户提供必要的信息,并且使用credentialWithUser:password:persistence:创建NSURLCredential对象。
- HTTP摘要验证(NSURLAuthenticationMethodHTTPDigest),类似基础验证,需要用户名和密码。(该摘要是自动生成的。)提示用户提供必要的信息,并使用credentialWithUser:password:persistence:创建NSURLCredential对象。
- 客户端证书验证(NSURLAuthenticationMethodClientCertificate)要求系统身份和所有必要的证书通过服务器进行身份验证。使用credentialWithIdentity:certificates:persistence:创建NSURLCredential对象。
- 服务器信任验证(NSURLAuthenticationMethodServerTrust)需要由验证挑战的保护空间提供信任(trust)。使用credentialForTrust:创建NSURLCredential对象。
在你创建了NSURLCredential对象之后,使用提供的完成处理程序代码块把这个对象传递给验证挑战的发送者。
无证书连接
如果委托选择不向验证挑战提供证书,它可以尝试无验证的继续连接。把下面值之一传递给完成处理程序代码块:
- NSURLSessionAuthChallengePerformDefaultHandling处理请求,就像委托不提供委托方法来处理挑战一样。
- NSURLSessionAuthChallengeRejectProtectionSpace拒绝挑战。根据服务器响应允许的验证类型,URL加载类或许调用这个委托方法不止一次,以获取额外的保护空间。
取消连接
通过给提供的完成处理程序代码块传递NSURLSessionAuthChallengeCancelAuthenticationChallenge,委托也可以选择取消验证挑战。
验证样例
代码清单4-1 展示了,通过创建NSURLCredential实例响应挑战。该实例使用通过应用程序的首选项提供的用户名和密码创建。如果该验证之前失败,它会取消认证挑战并通知用户。
代码清单 4-1 使用URLSession:didReceiveChallenge:completionHandler:委托方法的示例
- (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);
}
}
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)
}
如果验证挑战没有被会话或任务委托处理,且证书不可用或使用它们验证失败,continueWithoutCredentialForAuthenticationChallenge消息会通过底层的实现发送。
执行自定义的TLS链验证
在NSURL系列API中,TLS链验证通过app验证委托方法处理,而不是提供验证用户(或app)的证书给服务器。App会在TLS握手期间检查服务器提供的证书,然后告诉URL加载系统是否应该接受或拒绝这些证书。
如果你需要以非标准的方式执行链验证(例如,为测试接收一个特定的自签名证书),app必须实现URLSession:didReceiveChallenge:completionHandler: 或 URLSession:task:didReceiveChallenge:completionHandler:委托方法。如果两者都实现,则会话级别的方法负责处理验证。
在你的验证处理委托方法中,你应该检查挑战保护空间是否有NSURLAuthenticationMethodServerTrust的验证类型,如果有,从保护空间获取serverTrust信息。