{{item}}
{{item.title}}
{{items.productName}}
{{items.price}}/年
{{item.title}}
部警SSL证书可实现网站HTTPS加密保护及身份的可信认证,防止传输数据的泄露或算改,提高网站可信度和品牌形象,利于SEO排名,为企业带来更多访问量,这也是网络安全法及PCI合规性的必备要求
前往SSL证书开发者在使用requests时,对 “如何手动校验证书”“如何解决证书验证失败” 等关键问题缺乏深入理解,可能导致安全漏洞或请求异常。本文将从requests库的 SSL 处理原理出发,详细讲解证书有效性校验的实现方法、常见场景与问题解决方案,帮助开发者构建安全可靠的 HTTPS 请求逻辑。
在深入手动校验前,需先理解requests库默认的SSL证书处理逻辑 —— 这是后续自定义校验的基础,也是解决 “为何有时请求成功,有时报 SSL 错误” 的关键。
requests库基于urllib3实现底层网络通信,其默认的 SSL 校验逻辑遵循以下规则:
a. 证书是否在有效期内(未过期且未提前生效);
b. 证书的 “主题备用名称(SAN)” 是否与请求的域名匹配(防止域名劫持);
c. 证书链是否完整(包含服务器证书、中间证书,且能追溯至根证书);
d. 证书是否被列入 “证书吊销列表(CRL)” 或通过 “在线证书状态协议(OCSP)” 验证为有效(部分环境默认开启)。
例如,当使用requests.get("https://www.baidu.com")发送请求时,requests会自动校验百度服务器的SSL证书:验证其 SAN 包含 “baidu.com”,证书链能追溯至 DigiCert 根证书,且证书未过期,最终校验通过,返回响应。
若目标服务器的SSL证书不符合上述校验规则,requests会抛出requests.exceptions.SSLError异常,常见场景包括:
场景 1:自签名证书:服务器使用 OpenSSL 等工具生成的自签名证书(未经过权威 CA 签名),因不在requests默认信任的根证书列表中,校验失败;
场景 2:证书链不完整:服务器仅配置了 “服务器证书”,未提供 “中间证书”,导致requests无法从服务器证书追溯至根证书,校验失败;
场景 3:域名不匹配:证书的 SAN 中未包含请求的域名(如证书为 “example.com”,但请求 “test.example.com”);
场景 4:证书过期或吊销:证书已超过有效期,或因私钥泄露等原因被 CA 吊销;
场景 5:弱协议 / 加密套件:服务器仅支持 SSLv3、TLS 1.0 等存在漏洞的协议,或使用强度不足的加密套件,requests出于安全考虑拒绝建立连接。
例如,访问使用自签名证书的本地测试服务器时,执行requests.get("https://127.0.0.1:8443")会抛出类似以下的错误:
1 requests.exceptions.SSLError: HTTPSConnectionPool(host='127.0.0.1', port=8443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1131)')))根据实际开发需求(如测试环境、生产环境、私有服务访问),requests提供了三种不同的SSL证书校验方式,从 “完全默认” 到 “高度自定义”,覆盖不同安全等级的场景。
适用场景:访问公网中使用权威 CA 颁发证书的服务(如百度、谷歌、GitHub 等),无需自定义校验逻辑,追求最高安全性。
实现方法:直接使用requests的get/post等方法,无需添加任何 SSL 相关参数,requests会自动完成证书校验。
代码示例:
1 importrequests
2
3 # 访问使用权威CA证书的服务,默认校验SSL证书
4 try:
5 response =requests.get("https://github.com")
6 print("请求成功,状态码:", response.status_code) # 输出200表示校验通过
7 exceptrequests.exceptions.SSLError as e:
8 print("SSL证书校验失败:", str(e))优势与注意事项:
a. 若系统根证书未更新(如旧版 Linux 未安装 Let’s Encrypt 的根证书),可能导致对使用 Let’s Encrypt 证书的服务校验失败,需手动更新系统根证书(如 Ubuntu 执行sudo apt update && sudo apt install ca-certificates);
b. 生产环境中禁止关闭默认校验(如设置verify=False),否则会面临中间人攻击风险。
适用场景:访问使用自签名证书或私有 CA(如企业内部 CA)颁发证书的服务(如本地测试服务器、企业内网 API),需明确指定 “可信任的证书”,避免信任系统所有根证书。
实现原理:通过verify参数指定本地的 “证书文件路径”(支持 PEM 格式),requests会仅信任该证书(或证书文件中的所有证书),并基于此验证服务器证书的有效性。
首先需从目标服务器获取证书文件(PEM 格式),常用方法有两种:
在终端执行以下命令,从服务器提取证书并保存为server.crt文件(以本地服务器127.0.0.1:8443为例):
1 openssl s_client -connect 127.0.0.1:8443 -showcerts < /dev/null 2>/dev/null | openssl x509 -outform PEM > server.crt该命令会建立与服务器的 SSL 连接,提取服务器证书并转换为 PEM 格式保存。
在 Chrome/Firefox 中访问目标服务器,点击地址栏的 “安全锁”→“证书”→“详细信息”→“复制到文件”,选择 “Base64 编码 X.509 (.CER)” 格式(与 PEM 格式兼容),保存为server.crt。
将获取的server.crt文件放在项目目录中,通过verify参数指定其路径,requests会基于该证书校验服务器证书。
代码示例:
1 importrequests
2
3 # 手动指定信任的证书文件路径(相对路径或绝对路径)
4 cert_file = "server.crt"
5
6 try:
7 # verify参数指定证书文件,requests仅信任该证书
8 response =requests.get("https://127.0.0.1:8443", verify=cert_file)
9 print("请求成功,响应内容:", response.text[:100]) # 输出前100字符表示校验通过
10 exceptrequests.exceptions.SSLError as e:
11 print("SSL证书校验失败:", str(e))若目标服务器的证书链包含多个中间证书,或需信任整个私有 CA 的证书(而非单个服务器证书),可将所有需信任的证书(服务器证书、中间证书、CA 根证书)合并到同一个 PEM 文件中,verify参数指定该合并文件即可。
合并证书方法:
用文本编辑器打开所有 PEM 格式的证书文件,按 “服务器证书→中间证书→CA 根证书” 的顺序拼接,保存为trusted_certs.pem(每个证书需完整包含-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----标记)。
代码示例:
1 # 信任多个证书(合并后的证书文件)
2 response =requests.get("https://private-api.company.com", verify="trusted_certs.pem")适用场景:高安全需求场景(如金融交易、企业核心 API),不仅需要客户端校验服务器证书,服务器也需校验客户端证书,确保双方身份均合法(即 “双向认证”)。
实现原理:
客户端证书通常由服务提供方(如企业 IT 部门、API 服务商)颁发,格式为 PEM 或 PFX:
1 # 将PFX文件转换为PEM格式的证书和私钥
2 openssl pkcs12 -in client.pfx -clcerts -nokeys -out client.crt
3 openssl pkcs12 -in client.pfx -nocerts -out client.key -nodes # -nodes表示不加密私钥通过cert参数传递客户端证书与私钥的路径(元组形式:(cert_file, key_file)),verify参数指定服务器的信任证书,实现双向校验。
代码示例:
1 importrequests
2
3 # 客户端证书与私钥路径(PEM格式)
4 client_cert = ("client.crt", "client.key")
5 # 服务器信任证书路径(可是服务器证书或CA证书)
6 server_cert = "server_ca.crt"
7
8 try:
9 # cert参数:客户端证书与私钥;verify参数:校验服务器证书
10 response =requests.get(
11 "https://secure-api.bank.com/transaction",
12 cert=client_cert,
13 verify=server_cert
14 )
15 print("双向SSL校验成功,交易数据:", response.json())
16 exceptrequests.exceptions.SSLError as e:
17 print("双向SSL校验失败:", str(e))
18 except Exception as e:
19 print("请求异常:", str(e))注意事项:
在使用requests校验SSL证书时,开发者常遇到 “校验失败”“私钥加密”“协议不兼容” 等问题,以下是高频问题的解决方案:
错误表现:
1 requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1131)解决方案:
1 # 临时关闭SSL校验(生产环境禁止使用)
2 response =requests.get("https://127.0.0.1:8443", verify=False)
3 # 关闭requests的警告(可选,避免控制台输出安全警告)
4 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)错误表现:
1 requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)原因:服务器仅返回 “服务器证书”,未返回 “中间证书”,requests无法从服务器证书追溯至根证书。
解决方案:
方法 1:服务器端修复(推荐):在服务器配置中添加完整的证书链(将服务器证书与中间证书合并为一个文件,如 Nginx 的ssl_certificate参数指定合并后的文件);
方法 2:客户端补充中间证书:从 CA 官网下载对应的中间证书(如 Let’s Encrypt 的中间证书可从其官网获取),与服务器证书合并为trusted_certs.pem,verify参数指定该文件(参考 2.2.3 节)。
错误表现:
1 requests.exceptions.SSLError: [SSL: BAD_DECRYPT] bad decrypt (_ssl.c:2633)原因:客户端私钥文件(client.key)在生成时被加密(如 OpenSSL 转换时未加-nodes参数),requests无法读取加密的私钥。
解决方案:
方法 1:解密私钥:在终端通过 OpenSSL 解密私钥,生成未加密的私钥文件:
1 openssl rsa -in encrypted_client.key -out client.key # 执行后输入私钥的加密密码方法 2:代码中指定私钥密码(进阶):若私钥必须加密存储,可通过ssl模块手动构建 SSL 上下文,传入私钥密码,再传递给 requests:
1 importrequests
2 import ssl
3 fromrequests.adapters import HTTPAdapter
4 from urllib3.poolmanager import PoolManager
5 from urllib3.util.ssl_ import create_ssl_context
6
7 # 自定义SSL上下文,指定私钥密码
8 class SSLAdapter(HTTPAdapter):
9 def __init__(self, ssl_context=None, **kwargs):
10 self.ssl_context = ssl_context
11 super().__init__(** kwargs)
12
13 def init_poolmanager(self, connections, maxsize, block=False):
14 self.poolmanager = PoolManager(
15 num_pools=connections,
16 maxsize=maxsize,
17 block=block,
18 ssl_context=self.ssl_context
19 )
20
21 # 创建支持私钥密码的SSL上下文
22 context = create_ssl_context()
23 # 加载客户端证书与加密私钥,传入密码(b"password"为私钥的加密密码)
24 context.load_cert_chain(certfile="client.crt", keyfile="encrypted_client.key", password=b"your_key_password")
25
26 # 为requests会话绑定自定义SSL上下文
27 s =requests.Session()
28 s.mount("https://", SSLAdapter(context=context))
29
30 # 发送请求(无需再指定cert参数)
31 response = s.get("https://secure-api.bank.com", verify="server_ca.crt")
32 print(response.status_code)错误表现:
1 requests.exceptions.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1131)原因:服务器仅支持旧版协议(如 TLS 1.0)或特定加密套件,而requests默认禁用不安全的旧协议,导致握手失败。
解决方案:
1 importrequests
2 import ssl
3 fromrequests.adapters import HTTPAdapter
4 from urllib3.poolmanager import PoolManager
5 from urllib3.util.ssl_ import create_ssl_context
6
7 # 自定义Adapter,启用TLS 1.0(仅用于兼容旧服务器)
8 class TLS10Adapter(HTTPAdapter):
9 def init_poolmanager(self, connections, maxsize, block=False):
10 context = create_ssl_context(ssl.PROTOCOL_TLSv1) # 启用TLS 1.0
11 # 可选:指定服务器支持的加密套件
12 context.set_ciphers("ECDHE-RSA-AES256-SHA")
13 self.poolmanager = PoolManager(
14 num_pools=connections,
15 maxsize=maxsize,
16 block=block,
17 ssl_context=context
18 )
19
20 s =requests.Session()
21 s.mount("https://", TLS10Adapter()) # 为HTTPS请求绑定TLS 1.0 Adapter
22
23 # 发送请求
24 response = s.get("https://old-server.example.com", verify="server.crt")
25 print(response.status_code)对于复杂场景(如自定义证书吊销校验、动态信任证书),仅靠requests的verify和cert参数无法满足需求,需通过 Python 内置的ssl模块手动构建 SSL 上下文,实现高度自定义的校验逻辑。
ssl.SSLContext是 Python 中控制 SSL 行为的核心类,通过它可实现:
以下示例通过ssl.SSLContext的verify_mode和check_hostname参数,结合自定义验证回调函数,实现 “仅信任特定域名且证书未过期” 的校验逻辑。
1 importrequests
2 import ssl
3 fromrequests.adapters import HTTPAdapter
4 from urllib3.poolmanager import PoolManager
5 from urllib3.util.ssl_ import create_ssl_context
6 from datetime import datetime
7
8 def custom_verify_callback(conn, cert, errno, depth, ok):
9 """
10 自定义证书验证回调函数
11 :param conn: SSL连接对象
12 :param cert: 证书信息(字典格式,包含subject、expire_date等)
13 :param errno: 错误码(0表示无错误)
14 :param depth: 证书在链中的深度(0表示服务器证书)
15 :param ok: 前序验证结果(True/False)
16 :return: 最终验证结果(True/False)
17 """
18 # 仅验证服务器证书(depth=0)
19 if depth != 0:
20 return ok
21
22 # 1. 校验证书是否在有效期内
23 expire_date = datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
24 if expire_date < datetime.utcnow():
25 print(f"证书已过期,过期时间:{expire_date}")
26 return False
27
28 # 2. 校验证书域名是否为预期域名(如"api.example.com")
29 expected_domain = "api.example.com"
30 # 从证书的subjectAltName中提取域名
31 san = cert.get("subjectAltName", [])
32 cert_domains = [name for (type_, name) in san if type_ == "DNS"]
33 # 若未设置SAN,从subject的CN字段提取域名
34 if not cert_domains:
35 cn = [item[0][1] for item in cert["subject"] if item[0][0] == "commonName"][0]
36 cert_domains = [cn]
37 # 检查预期域名是否在证书域名列表中
38 if expected_domain not in cert_domains:
39 print(f"证书域名不匹配,预期:{expected_domain},实际:{cert_domains}")
40 return False
41
42 # 3. 其他自定义校验(如校验证书颁发者、吊销状态等)
43 # ...
44
45 return True
46
47 # 构建自定义SSL上下文
48 context = create_ssl_context(ssl.PROTOCOL_TLSv1_2) # 仅启用TLS 1.2
49 # 加载信任的CA证书(或服务器证书)
50 context.load_verify_locations("trusted_ca.crt")
51 # 启用证书验证
52 context.verify_mode = ssl.CERT_REQUIRED
53 # 禁用自动域名校验(由自定义回调处理)
54 context.check_hostname = False
55 # 设置自定义验证回调函数
56 context.set_verify_mode(ssl.CERT_REQUIRED)
57 context.set_default_verify_paths()
58 ssl.verify_callback = custom_verify_callback # 注意:此处需将回调绑定到ssl模块
59
60 # 为requests会话绑定自定义上下文
61 class CustomSSLAdapter(HTTPAdapter):
62 def init_poolmanager(self, connections, maxsize, block=False):
63 self.poolmanager = PoolManager(
64 num_pools=connections,
65 maxsize=maxsize,
66 block=block,
67 ssl_context=context
68 )
69
70 s =requests.Session()
71 s.mount("https://", CustomSSLAdapter())
72
73 # 发送请求
74 try:
75 response = s.get("https://api.example.com/data")
76 print("自定义校验成功,响应:", response.text[:100])
77 exceptrequests.exceptions.SSLError as e:
78 print("自定义校验失败:", str(e))上述文章详细覆盖了requests库校验SSL证书的多种场景与问题解决方法,若你在实际开发中遇到特定场景(如结合框架使用、处理特殊证书格式),或有进一步优化需求,可随时告知,我会为你补充更针对性的内容。
Dogssl.cn拥有20年网络安全服务经验,提供构涵盖国际CA机构Sectigo、Digicert、GeoTrust、GlobalSign,以及国内CA机构CFCA、沃通、vTrus、上海CA等数十个SSL证书品牌。全程技术支持及免费部署服务,如您有SSL证书需求,欢迎联系!