Swift中如何为HTTPS请求添加客户端证书(无需SSL固定)
嗨Frank,我来帮你搞定在Swift里给每个HTTP请求附加客户端证书的问题!下面是一步一步的实现方案和代码示例:
实现客户端证书随请求发送的完整流程
1. 先把证书转成iOS友好的格式
通常我们会把.crt和.key打包成PKCS#12格式的.p12文件,这样在代码里加载更方便。你可以用OpenSSL命令转换:
openssl pkcs12 -export -in your_cert.crt -inkey your_key.key -out client_cert.p12 -name "ClientCertificate"
执行命令时会要求你设置一个密码,记得记下来后面要用。然后把生成的.p12文件拖进Xcode项目,勾选"Copy items if needed",确保它被加入到目标的Build Phases -> Copy Bundle Resources里。
2. 从Bundle加载并解析证书
写个工具方法把.p12里的证书和私钥提取出来:
import Foundation import Security func loadClientCertificate() -> (SecCertificate, SecKey)? { // 替换成你的p12文件名 guard let p12Path = Bundle.main.path(forResource: "client_cert", ofType: "p12"), let p12Data = try? Data(contentsOf: URL(fileURLWithPath: p12Path)) else { print("找不到或无法加载p12文件") return nil } // 这里填你生成p12时设置的密码 let options: [CFString: Any] = [kSecImportExportPassphrase: "你的p12密码"] var items: CFArray? let status = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &items) guard status == errSecSuccess, let itemArray = items as? [[CFString: Any]], let identityDict = itemArray.first, let identity = identityDict[kSecImportItemIdentity] as? SecIdentity else { print("解析p12失败,错误码:\(status)") return nil } var certificate: SecCertificate? SecIdentityCopyCertificate(identity, &certificate) var privateKey: SecKey? SecIdentityCopyPrivateKey(identity, &privateKey) guard let cert = certificate, let key = privateKey else { print("无法从p12中提取证书或私钥") return nil } return (cert, key) }
3. 配置URLSession自动携带证书
我们需要自定义URLSessionDelegate来处理服务器发起的客户端证书验证挑战:
class CertificateSessionDelegate: NSObject, URLSessionDelegate { private let clientCert: SecCertificate private let privateKey: SecKey init(cert: SecCertificate, key: SecKey) { self.clientCert = cert self.privateKey = key super.init() } func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { // 只处理客户端证书验证的挑战 guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate else { // 其他验证(比如服务器证书)交给系统默认处理 completionHandler(.performDefaultHandling, nil) return } // 创建包含证书和私钥的凭证 guard let identity = SecIdentityCreateWithCertificate(nil, clientCert, nil) else { completionHandler(.cancelAuthenticationChallenge, nil) return } let credential = URLCredential(identity: identity, certificates: [clientCert], persistence: .forSession) completionHandler(.useCredential, credential) } } // 初始化带证书支持的URLSession guard let (cert, key) = loadClientCertificate() else { fatalError("客户端证书加载失败,请检查文件和密码") } let sessionDelegate = CertificateSessionDelegate(cert: cert, key: key) let secureSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
4. 发起带证书的请求示例
现在用这个自定义的secureSession发起请求,就会自动在会话中携带客户端证书了:
func sendAuthenticatedRequest() { guard let apiURL = URL(string: "https://你的服务器API地址/接口路径") else { print("无效的API地址") return } var request = URLRequest(url: apiURL) request.httpMethod = "GET" // 根据你的需求修改请求方法 let task = secureSession.dataTask(with: request) { data, response, error in if let error = error { print("请求出错:\(error.localizedDescription)") return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { print("服务器返回异常响应") return } if let data = data, let responseContent = String(data: data, encoding: .utf8) { print("请求成功,响应内容:\(responseContent)") } } task.resume() } // 调用请求 sendAuthenticatedRequest()
额外提醒
- 确保你的服务器已经配置好,能够接受客户端证书验证,并且信任你的客户端证书的CA根证书
- 如果不想转成
.p12,也可以分别加载.crt和.key,但需要先把.key转成DER格式,步骤会更繁琐,还是推荐用.p12的方式 - 如果用Alamofire框架,核心逻辑也是类似的,只需要配置对应的客户端证书管理器即可
内容的提问来源于stack exchange,提问作者Frank van Vliet
相关产品推荐
相关产品推荐

