在調(diào)用Https地址請求數(shù)據(jù)時,我們會遇到證書驗證的問題。
關于證書調(diào)用的方法。主要有兩個代理
這兩個方法是不同的,一個是session負責處理,一個是task負責處理,處理的方式一樣,一般我們會用session來處理。
Requests credentials from the delegate in response to a session-level authentication request from the remote server.
從委托請求憑據(jù),以響應來自遠程服務器的會話級身份驗證請求。
optional func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
- When a remote server asks for client certificates or Windows NT LAN Manager (NTLM) authentication, to allow your app to provide appropriate credentials
- When a session first establishes a connection to a remote server that uses SSL or TLS, to allow your app to verify the server’s certificate chain
If you do not implement this method, the session calls its delegate’s urlSession(_:task:didReceive:completionHandler:) method instead.
翻譯:
這種方法在兩種情況下調(diào)用:
當遠程服務器請求客戶端證書或Windows NT LAN Manager (NTLM)身份驗證時,允許您的應用程序提供適當?shù)膽{據(jù)
當會話首次建立到使用SSL或TLS的遠程服務器的連接時,允許您的應用程序驗證服務器的證書鏈
如果不實現(xiàn)此方法,則會話將調(diào)用其委托的urlSession(_:task:didReceive:completionHandler:)方法。
當sesion的代理方法未實現(xiàn)時,會執(zhí)行下面的這種方式,如果兩種方法都未實現(xiàn)的時候會怎么處理呢?我們下面會說到。
Requests credentials from the delegate in response to an authentication request from the
remote server.
從委托請求憑據(jù)以響應來自遠程服務器的身份驗證請求。
optional func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
This method handles task-level authentication challenges. The URLSessionDelegate protocol also provides a session-level authentication delegate method. The method called depends on the type of authentication challenge:
For session-level challenges—NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate, or NSURLAuthenticationMethodServerTrust—the NSURLSession object calls the session delegate’s urlSession(_:didReceive:completionHandler:) method. If your app does not provide a session delegate method, the NSURLSession object calls the task delegate’s urlSession(_:task:didReceive:completionHandler:) method to handle the challenge.
For non-session-level challenges (all others), the URLSession object calls the session delegate’s urlSession(_:task:didReceive:completionHandler:) method to handle the challenge. If your app provides a session delegate and you need to handle authentication, then you must either handle the authentication at the task level or provide a task-level handler that calls the per-session handler explicitly. The session delegate’s urlSession(_:didReceive:completionHandler:) method is not called for non-session-level challenges.
翻譯:
此方法處理任務級身份驗證挑戰(zhàn)。URLSessionDelegate協(xié)議還提供了一個任務級的身份驗證委托方法。所調(diào)用的方法取決于認證挑戰(zhàn)的類型:
對于會話級別的挑戰(zhàn)——nsurlauthenticationmethodntlm、NSURLAuthenticationMethodNegotiate、NSURLAuthenticationMethodClientCertificate或nsurlauthenticationmethodservertrust——NSURLSession對象調(diào)用會話委托的urlSession(_:didReceive:completionHandler:)方法。如果你的應用程序沒有提供一個會話委托方法,NSURLSession對象會調(diào)用任務委托的urlSession(_:task:didReceive:completionHandler:)方法來處理這個挑戰(zhàn)。
對于非會話級別的挑戰(zhàn)(所有其他挑戰(zhàn)),URLSession對象調(diào)用會話委托的URLSession (_:task:didReceive:completionHandler:)方法來處理該挑戰(zhàn)。如果您的應用程序提供了一個會話委托,而您需要處理身份驗證,那么您必須在任務級處理身份驗證,或者提供一個顯式調(diào)用每個會話處理程序的任務級處理程序。對于非會話級挑戰(zhàn),不會調(diào)用會話委托的urlSession(_:didReceive:completionHandler:)方法。
這兩個代理方法的總結(jié)就是,如果是session級別的驗證,那么可以使用任一一種方法解決。
如果是非會話級別的驗證(on-session-level challenges (all others)),那么session級別不能替代task級別。
- 不同的請求驗證方式
NSURLAuthenticationMethodClientCertificate
此保護空間使用客戶端證書身份驗證。
NSURLAuthenticationMethodNegotiate
協(xié)商在這個保護空間中使用Kerberos還是NTLM身份驗證。
NSURLAuthenticationMethodNTLM
對這個保護空間使用NTLM身份驗證。
NSURLAuthenticationMethodServerTrust
為這個保護空間執(zhí)行服務器信任身份驗證(證書驗證)。
在處理會話的時候,將分為三個部分
- 驗證服務器證書 NSURLAuthenticationMethodServerTrust
- 服務器驗證本地 NSURLAuthenticationMethodClientCertificate
- 其他 NSURLAuthenticationMethodNTLM,NSURLAuthenticationMethodNegotiate
一般我們會用到兩種,一種是我們驗證服務器,一種是服務器驗證客戶端。
NSURLAuthenticationMethodServerTrust
如果我們驗證服務器的話,可以有兩種方式
- 直接驗證通過
- 使用代碼和證書驗證
//不做任何驗證,直接信任服務器
static private func trustServer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
let disposition = URLSession.AuthChallengeDisposition.useCredential
let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
return (disposition, credential)
}
//驗證服務器證書
static private func trustServerWithCer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
//獲取服務器發(fā)送過來的證書
let serverTrust:SecTrust = challenge.protectionSpace.serverTrust!
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
//加載本地CA證書
let cerPath = Bundle.main.path(forResource: "你本地的cer證書文件名", ofType: "cer")!
let cerUrl = URL(fileURLWithPath:cerPath)
let localCertificateData = try! Data(contentsOf: cerUrl)
if (remoteCertificateData.isEqual(localCertificateData) == true) {
//服務器證書驗證通過
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: serverTrust)
} else {
//服務器證書驗證失敗
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
return (disposition, credential)
}
NSURLAuthenticationMethodClientCertificate
//發(fā)送客戶端證書交由服務器驗證
static private func sendClientCer() -> (URLSession.AuthChallengeDisposition, URLCredential?) {
let disposition = URLSession.AuthChallengeDisposition.useCredential
var credential: URLCredential?
//獲取項目中P12證書文件的路徑
let path: String = Bundle.main.path(forResource: "你本地的p12證書文件名", ofType: "p12")!
let PKCS12Data = NSData(contentsOfFile:path)!
let key : NSString = kSecImportExportPassphrase as NSString
let options : NSDictionary = [key : "p12證書的密碼"] //客戶端證書密碼
var items: CFArray?
let error = SecPKCS12Import(PKCS12Data, options, &items)
if error == errSecSuccess {
let itemArr = items! as Array
let item = itemArr.first!
let identityPointer = item["identity"];
let secIdentityRef = identityPointer as! SecIdentity
let chainPointer = item["chain"]
let chainRef = chainPointer as? [Any]
credential = URLCredential.init(identity: secIdentityRef, certificates: chainRef, persistence: URLCredential.Persistence.forSession)
}
return (disposition, credential)
}