双向SSL认证(mTLS)要求客户端和服务端互相验证身份,而非仅服务端单向验证;requests默认不发送客户端证书,需显式传入cert和verify参数,否则报SSLError。

什么是双向SSL认证,为什么 requests 会报错
双向SSL认证(mTLS)要求客户端和服务端都提供有效证书,不是只靠服务端证书验证。requests 默认只校验服务端证书,遇到需要客户端证书的站点会直接抛出 SSLError,典型错误是 ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca 或更常见的 requests.exceptions.SSLError: [Errno 1] _ssl.c:... : certificate verify failed——这往往不是证书链问题,而是根本没传客户端证书。
关键点:必须同时提供客户端证书文件(cert)和私钥(通常合并为一个 .pem 文件),且服务端 CA 证书(verify)也要显式指定,不能依赖系统默认信任库。
用 requests 发起带双向认证的请求
核心是 requests.get() 的 cert 和 verify 参数:
-
cert:必须是长度为 2 的元组,如(client_cert_path, client_key_path);如果证书和私钥已合并为单个 PEM 文件,可只传字符串路径(如"client.pem") -
verify:必须指向服务端使用的 CA 根证书(ca_bundle.crt),不能设为True或省略,否则会走系统默认信任库,大概率失败 - 若私钥有密码,
cert元组需为三元组:(cert_path, key_path, "password")(requests 2.25.0+ 支持)
示例:
立即学习“Python免费学习笔记(深入)”;
import requests
response = requests.get(
"https://api.example.com/data",
cert=("client.crt", "client.key"),
verify="ca-bundle.crt"
)
print(response.status_code)
常见失败原因和绕过陷阱
很多调试卡在“证书明明是对的却连不上”,实际是以下细节被忽略:
- 证书格式必须是 PEM(以
-----BEGIN CERTIFICATE-----开头),不是 DER 或 PFX;可用openssl x509 -in cert.der -inform DER -out cert.pem转换 -
client.key文件权限不能太宽松(Linux/macOS 下若权限为 644 以上,OpenSSL 可能拒绝读取,建议chmod 600 client.key) - 服务端可能要求 SNI(Server Name Indication),requests 默认开启,但某些老旧 OpenSSL 版本或自定义 SSL 上下文时会被禁用——确认你用的是 requests ≥ 2.20 且 OpenSSL ≥ 1.0.2
- 不要尝试用
verify=False绕过——这会跳过服务端证书校验,但双向认证中客户端证书仍需被服务端接受,而verify=False不影响客户端证书发送,只是让 requests 不校验服务端响应,反而掩盖真实问题
调试阶段必须检查的三项输出
抓包和日志比盲试更高效:
- 加
logging.basicConfig(level=logging.DEBUG),看 requests 是否真的发出了ClientHello并携带了证书(搜索日志里的send: '....CERTIFICATE....') - 用
curl -v --cert client.pem --cacert ca-bundle.crt https://api.example.com对比行为,curl 成功而 Python 失败,基本是路径、密码或格式问题 - 服务端返回的 HTTP 状态码如果是
400或403,大概率是证书未被服务端信任(CA 错、证书过期、域名不匹配),而不是 TLS 握手失败
真正难调的从来不是代码怎么写,而是搞清服务端到底期望哪个 CA、证书 subject 是否含特定 OU 字段、是否强制要求 OCSP stapling——这些信息通常藏在对方提供的接入文档 PDF 里,而不是错误提示中。


















