You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:07:20