目录
1. Challenge 类型
2. 实现哪一个代理方法
3. 判断Authentication Challenge的类型
4. 创建Credential实例
5. 调用Completion Handler
6. 对于错误的处理
NSURLSession 引入后, request被包装进data task或者download task中发送至server, 如果server需要客户端的认证, session task 会尝试处理,如果无法处理则会发起客户端的challenge,调用Session 或者 task didReceiveChallenge的代理方法。如何handle一个challenge, 苹果有官方文档:
https://developer.apple.com/documentation/foundation/url_loading_system/handling_an_authentication_challenge?language=objc
主要就是要实现didReceiveChallenge的代理方法并在代理方法中handle challenge, 对服务器做出正确回应。
Challenge类型可以从challenge的 property NSURLProtectionSpace
authenticationMethod 获取,主要有以下几种类型:
Session-Wide Authentication Challenges
NSURLAuthenticationMethodClientCertificate
Use client certificate authentication for this protection space.
NSURLAuthenticationMethodNegotiate - 如果windows authenticaiton 配置了Kerberos和NTLM,需要向用户端确认是哪个,所以是negotiate
Negotiate whether to use Kerberos or NTLM authentication for this protection space.
NSURLAuthenticationMethodNTLM
- Windows authenticaitonUse NTLM authentication for this protection space.
NSURLAuthenticationMethodServerTrust
- HTTPS或者自签名验证证书走的就是这个challenge typePerform server trust authentication (certificate validation) for this protection space.
Task-Specific Authentication Challenges
NSURLAuthenticationMethodDefault
Use the default authentication method for a protocol.
NSURLAuthenticationMethodHTMLForm
Use HTML form authentication for this protection space.
NSURLAuthenticationMethodHTTPBasic
Use HTTP basic authentication for this protection space.
NSURLAuthenticationMethodHTTPDigest
Use HTTP digest authentication for this protection space.
NSURLSessionDelegate
URLSession:didReceiveChallenge:completionHandler:
方法。一旦验证成功,通过了这种challenge,之后通过这个Session创建的所有task都会顺利通过。具体包含的challenge类型分别为:NSURLAuthenticationMethodClientCertificate
NSURLSessionTaskDelegate
URLSession:task:didReceiveChallenge:completionHandler: 方法。这个一般是需要用户名/密码验证的challenge。每个task处理自己对应的challenge。
比如,当发送一个HTTP request,由于是task-specific的challenge,因此实现NSURLSessionTaskDelegate
URLSession:task:didReceiveChallenge:completionHandler: 方法。具体流程如图:
获取challenge类型:在代理方法中,可以通过方法提供的challenge实例,获取其protectionSpace变量进而通过authenticationMethod提取challenge类型,根据challenge类型判断是否可以处理以及如何处理。
处理challenge: 通过调用completionHandler来回应challenge, 传递 NSURLSessionAuthChallengeDisposition 参数到completionHandler 代表是如何处理这个challenge的,有可能是提供了所需的credential,有可能是使用默认处理方法,也有可能是无法处理,取消了request。
下面例子中是判断chanllenge type如果不是HTTPBasic,就用默认处理方式。completionHandler中第二个参数是credential,如果没有要提供的credentials,就默认为nil.
let authMethod = challenge.protectionSpace.authenticationMethod
guard authMethod == NSURLAuthenticationMethodHTTPBasic else {
completionHandler(.performDefaultHandling, nil)
return
}
如果需要提供credentials,就创建一个NSURLCredential实例
func credentialsFromUI() -> URLCredential? {
guard let username = usernameField.text, !username.isEmpty,
let password = passwordField.text, !password.isEmpty else {
return nil
}
return URLCredential(user: username, password: password,
persistence: .forSession)
}
一旦创建了credential实例,调用 completionHandler,将dispostion和credentials参数传递进去。
guard let credential = credentialOrNil else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
completionHandler(.useCredential, credential)
如果credentials是正确的并通过了server challenge, 上传或者下载task就会顺利开始。
completionHandler可以被暂时存储下来,比如,当向用户索要用户名密码的时候,可以将completionHandler传递给给其他方法或者暂时存储下来。但最终一定要传递给challenge,即使最后是cancel的操作。
如果credentials是错误的,被服务器拒绝了。这时,还会再调didReceiveChallenge,针对相同的request,相同的challenge。被rejected的credentials会被封装到challenge的proposedCredential里面。Challenge中的previousFailureCount也会+1,代表之前credentials被rejected了几次。通过这些property可以决定接下来做什么,比如,发送一个请求,如果受到server challenge,可以show出login的界面,而当用户provide了credentials,你传入completionHandler,之后发给server,如果server发现credentials有问题,会再次发起challenge,这时拿到的 challenge的previousFailureCount > 0, 此时可以show一个retry的string或者UI给user就好了。