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

如何在Swift中实现带客户端证书与密钥的HTTP请求(Python转译)

在Swift中使用客户端证书发起GET请求(对应Python requests.get带cert参数)

问题描述

需求:将以下Python代码转换为Swift实现,该代码通过requests.get传入客户端证书和密钥发起请求:

import requests
cert_file_path = "cert.pem"
key_file_path = "key.pem"

url = "https://example.com/resource"
params = {"param_1": "value_1", "param_2": "value_2"}
cert = (cert_file_path, key_file_path)
r = requests.get(url, params=params, cert=cert)

尝试的Swift代码无法获取响应,询问如何在Swift的URLSession中传递客户端证书和密钥,或其他实现方式:

import UIKit

let url = URL(string: "https://example.com/resource")!

let certPath = URL(fileURLWithPath: "certification.pem")
let certification = try? String(contentsOf: certPath)
let certKeyPath = URL(fileURLWithPath: "certification.key.pem")
let certKey = try? String(contentsOf: certKeyPath)

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        if let books = try? JSONDecoder().decode([Book].self, from: data) {
            print(books)
        } else {
            print("Invalid Response")
        }
    } else if let error = error {
        print("HTTP Request Failed \(error)")
    }
}

问题分析

你写的Swift代码只是读取了PEM格式的证书和密钥字符串,但没有将它们正确传递给URLSession,而且系统无法直接识别PEM字符串,需要先转换成SecCertificate和SecKey格式,同时还要配置URLSession的代理来处理客户端证书认证。另外,原Python代码中的请求参数params也没有在Swift代码中添加。

完整实现步骤

1. 实现PEM转SecCertificate和SecKey的工具函数

PEM格式需要先去除头尾标记,再Base64解码成DER格式,才能被系统解析:

import Foundation
import Security

// PEM证书转SecCertificate
func certificate(fromPEMFile path: URL) throws -> SecCertificate {
    let pemString = try String(contentsOf: path)
    let cleanedPEM = pemString
        .replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
        .replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
        .trimmingCharacters(in: .whitespacesAndNewlines)
    
    guard let derData = Data(base64Encoded: cleanedPEM) else {
        throw NSError(domain: "CertificateError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的PEM证书数据"])
    }
    
    guard let cert = SecCertificateCreateWithData(nil, derData as CFData) else {
        throw NSError(domain: "CertificateError", code: -2, userInfo: [NSLocalizedDescriptionKey: "创建SecCertificate失败"])
    }
    return cert
}

// PEM私钥转SecKey(假设无密码,若有密码需额外处理)
func privateKey(fromPEMFile path: URL) throws -> SecKey {
    let pemString = try String(contentsOf: path)
    let cleanedPEM = pemString
        .replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "")
        .replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "")
        .trimmingCharacters(in: .whitespacesAndNewlines)
    
    guard let derData = Data(base64Encoded: cleanedPEM) else {
        throw NSError(domain: "PrivateKeyError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的PEM私钥数据"])
    }
    
    let attributes: [CFString: Any] = [
        kSecAttrKeyType: kSecAttrKeyTypeRSA,
        kSecAttrKeyClass: kSecAttrKeyClassPrivate
    ]
    
    var error: Unmanaged<CFError>?
    guard let key = SecKeyCreateWithData(derData as CFData, attributes as CFDictionary, &error) else {
        throw error!.takeRetainedValue() as Error
    }
    return key
}

2. 配置带客户端证书认证的URLSession

创建自定义URLSession,通过代理URLSessionDelegate的方法提供客户端证书:

class CertificateSessionDelegate: NSObject, URLSessionDelegate {
    private let certificate: SecCertificate
    private let privateKey: SecKey
    
    init(certificate: SecCertificate, privateKey: SecKey) {
        self.certificate = certificate
        self.privateKey = privateKey
        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
        }
        
        let credential = URLCredential(identity: (privateKey, [certificate]), certificates: [certificate], persistence: .forSession)
        completionHandler(.useCredential, credential)
    }
}

3. 构建请求并发起调用

添加请求参数,创建配置好的URLSession并发起请求:

// 假设Book模型已定义
struct Book: Codable {
    // 根据实际响应字段定义属性
}

func makeAuthenticatedRequest() {
    do {
        // 1. 加载证书和私钥
        let certPath = URL(fileURLWithPath: "cert.pem") // 替换为你的证书路径
        let keyPath = URL(fileURLWithPath: "key.pem")   // 替换为你的密钥路径
        let cert = try certificate(fromPEMFile: certPath)
        let privateKey = try privateKey(fromPEMFile: keyPath)
        
        // 2. 构建带参数的URL
        var components = URLComponents(string: "https://example.com/resource")!
        components.queryItems = [
            URLQueryItem(name: "param_1", value: "value_1"),
            URLQueryItem(name: "param_2", value: "value_2")
        ]
        guard let url = components.url else {
            print("参数拼接后URL无效")
            return
        }
        
        // 3. 创建自定义URLSession
        let delegate = CertificateSessionDelegate(certificate: cert, privateKey: privateKey)
        let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: .main)
        
        // 4. 发起请求
        let task = session.dataTask(with: url) { data, response, error in
            defer { session.finishTasksAndInvalidate() }
            
            if let error = error {
                print("请求失败: \(error.localizedDescription)")
                return
            }
            
            guard let data = data else {
                print("未收到响应数据")
                return
            }
            
            do {
                let books = try JSONDecoder().decode([Book].self, from: data)
                print(books)
            } catch {
                print("响应解析失败: \(error.localizedDescription)")
            }
        }
        task.resume()
    } catch {
        print("加载证书/密钥失败: \(error.localizedDescription)")
    }
}

// 调用请求函数
makeAuthenticatedRequest()

注意事项

  • 确保证书和密钥文件的路径正确,在iOS项目中要将文件添加到项目目标的"Copy Bundle Resources"中,避免找不到文件。
  • 如果私钥有密码保护,需要修改privateKey(fromPEMFile:)函数,添加密码解密逻辑(可使用SecKeyImportExport相关API)。
  • 注意异常处理,示例中使用do-catch捕获错误,避免try?导致的隐藏问题。

内容的提问来源于stack exchange,提问作者jacobcan118

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:36:35