如何使用NSURLSession处理HTTPS证书错误?
嘿,我明白你遇到的问题了——当请求使用未授权CA的HTTPS服务器时,因为是客户端侧的证书信任错误,response会返回nil,只能拿到信息有限的Error对象,确实头疼!我来给你梳理下解决方案:
首先先把你的代码贴出来方便参考:
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in if(data != nil){ //... } else if(error != nil) { //... } //... }) task.resume()
1. 精准识别未授权CA类错误
系统抛出的证书相关错误都属于NSURLErrorDomain,我们可以通过错误码来精准判断是不是未授权CA/未知根证书的问题。常见的相关错误码有:
NSURLErrorServerCertificateUntrusted(错误码-1202):服务器证书未被信任NSURLErrorServerCertificateHasUnknownRoot(错误码-1203):证书的根CA不被系统信任NSURLErrorServerCertificateHasBadDate(错误码-1201):证书过期或日期无效
你可以在completionHandler里这样处理:
else if let error = error as NSError? { if error.domain == NSURLErrorDomain { switch error.code { case NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasUnknownRoot: // 这里就是你要处理的未授权CA错误 print("⚠️ 服务器使用未受信任的CA证书") // 可以在这里给用户提示或者执行自定义逻辑 case NSURLErrorServerCertificateHasBadDate: print("⚠️ 服务器证书已过期或日期无效") default: print("其他网络错误:\(error.localizedDescription)") } } }
2. 自定义信任策略(按需允许特定未授权CA)
如果你是在测试环境,需要临时允许这个未授权CA的服务器,或者生产环境要做证书固定(Certificate Pinning)来信任特定证书,可以通过自定义URLSessionDelegate来手动处理信任挑战。
示例代码(测试环境临时信任):
// 先创建一个遵守URLSessionDelegate的自定义代理类 class CustomSessionDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { // 只处理服务器信任类型的挑战 guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { // 其他类型挑战交给系统默认处理 completionHandler(.performDefaultHandling, nil) return } // 获取服务器的信任对象 guard let serverTrust = challenge.protectionSpace.serverTrust else { // 无法获取信任对象,拒绝连接 completionHandler(.cancelAuthenticationChallenge, nil) return } // 测试环境下直接信任该服务器(⚠️生产环境绝对不能这么做!) let credential = URLCredential(trust: serverTrust) completionHandler(.useCredential, credential) } } // 使用自定义代理创建URLSession let sessionDelegate = CustomSessionDelegate() let customSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) // 用这个自定义session发起请求 let task = customSession.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in if let data = data { // 处理返回数据 } else if let error = error as NSError? { // 这里可以继续处理其他错误 if error.domain == NSURLErrorDomain { // 错误判断逻辑和之前一致 } } }) task.resume()
生产环境建议:证书固定(Certificate Pinning)
如果是生产环境,绝对不能直接信任所有未知CA,建议固定服务器证书的公钥或指纹,只信任特定的证书:
// 在CustomSessionDelegate的didReceive方法里替换成以下逻辑 func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { completionHandler(.performDefaultHandling, nil) return } guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 获取服务器证书的公钥数据 guard let serverPublicKey = SecTrustCopyPublicKey(serverTrust), let serverPublicKeyData = SecKeyCopyExternalRepresentation(serverPublicKey, nil) as Data? else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 这里替换成你预先存储的服务器公钥(base64格式) let expectedPublicKeyBase64 = "你的服务器公钥base64字符串" guard let expectedPublicKeyData = Data(base64Encoded: expectedPublicKeyBase64) else { completionHandler(.cancelAuthenticationChallenge, nil) return } // 对比公钥,一致则信任 if serverPublicKeyData == expectedPublicKeyData { let credential = URLCredential(trust: serverTrust) completionHandler(.useCredential, credential) } else { // 公钥不一致,拒绝连接 completionHandler(.cancelAuthenticationChallenge, nil) } }
这样既可以处理未授权CA的错误,又能保证生产环境的安全性。
内容的提问来源于stack exchange,提问作者Mateusz Wlodarczyk
相关产品推荐
相关产品推荐

