在Python开发中,通过requests、urllib、aiohttp发起HTTPS请求时,经常遇到SSLError异常。这个错误本质上是SSL/TLS握手阶段,Python客户端无法验证服务端SSL证书的合法性,导致连接被OpenSSL底层直接断开。常见的报错片段包括:
- requests.exceptions.SSLError: HTTPSConnectionPool(...): Max retries exceeded with url: ...
- Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)'))
复制代码
除了证书校验失败,证书过期、域名不匹配、SSL版本不兼容、CA证书缺失、代理抓包工具干扰,都可能触发SSLError。下面按照实际排查顺序,给出几种可落地的处理方案。
一、临时关闭证书校验(仅限开发调试)
当目标环境使用自签名证书或内网测试证书时,可以在开发调试阶段临时关闭校验。requests库通过verify=False实现:
- import requests
- resp = requests.get("https://example.com", verify=False)
- print(resp.text)
复制代码
关闭校验后,requests会输出大量InsecureRequestWarning警告,可以通过urllib3的disable_warnings屏蔽:
- import requests
- from requests.packages.urllib3.exceptions import InsecureRequestWarning
- requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
- resp = requests.get("https://example.com", verify=False)
复制代码
如果使用urllib.request,需要手动创建不校验证书的SSL上下文:
- import urllib.request
- import ssl
- ctx = ssl.create_default_context()
- ctx.check_hostname = False
- ctx.verify_mode = ssl.CERT_NONE
- req = urllib.request.Request("https://example.com")
- with urllib.request.urlopen(req, context=ctx) as r:
- print(r.read())
复制代码
aiohttp的TCPConnector支持直接关闭SSL校验:
- import aiohttp
- import asyncio
- async def demo():
- connector = aiohttp.TCPConnector(ssl=False)
- async with aiohttp.ClientSession(connector=connector) as session:
- async with session.get("https://example.com") as resp:
- print(await resp.text())
- asyncio.run(demo())
复制代码
需要强调,verify=False只是让报错不再出现,并没有真正修复证书信任链。生产环境使用会引入中间人劫持风险,绝对禁止。
二、指定CA证书文件(推荐正式方案)
对于自签名服务,可以导出服务端CA根证书(.crt或.pem),然后在请求时指定该证书路径,让Python验证这个自定义CA:
- import requests
- resp = requests.get("https://example.com", verify="./ca-root.pem")
复制代码
更推荐在Session上全局设置,后续同一Session的所有请求都自动携带该CA:
- import requests
- session = requests.Session()
- session.verify = "./ca-root.pem"
- resp = session.get("https://example.com")
复制代码
这种方式既保留了证书校验能力,又能兼容自签名内网环境,是线上服务对接内部系统时的首选。
三、修复Python本地CA证书库
很多SSLError并不是网站证书有问题,而是Python运行环境找不到可信任的CA证书。requests依赖certifi包维护内置CA根证书集合,可以查看当前路径:
- import certifi
- print(certifi.where())
复制代码
输出的是certifi维护的ca.pem文件路径。如果这个文件缺失、损坏或过期,公网HTTPS请求就会报CERTIFICATE_VERIFY_FAILED。最简单的处理是升级certifi,更新根证书列表:
macOS上通过Python官网安装包安装Python后,常出现找不到系统CA证书的情况。需要手动执行证书安装脚本,注意按实际Python版本替换路径:
- /Applications/Python\ 3.12/Install\ Certificates.command
复制代码
Windows下如果使用精简版Python、绿色版Python,容易缺失CA链或OpenSSL动态库,建议直接安装官方Python,不要使用精简版。
四、SSL/TLS版本兼容问题
当报错信息中包含SSL_ERROR_UNSUPPORTED_VERSION时,说明服务端SSL/TLS版本过旧,与当前Python使用的OpenSSL版本不兼容。OpenSSL 3.0之后默认拒绝很多老旧的不安全TLS版本,内网老旧设备接口很容易触发该错误。
可以自定义SSL上下文,通过设置OP_LEGACY_SERVER_CONNECT选项来兼容老旧服务端。注意这个操作会降低安全性,需仔细评估风险:
- import ssl
- import requests
- from requests.adapters import HTTPAdapter
- from urllib3.poolmanager import PoolManager
- class SslAdapter(HTTPAdapter):
- def init_poolmanager(self, connections, maxsize, block=False):
- ctx = ssl.create_default_context()
- ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT
- self.poolmanager = PoolManager(
- num_pools=connections,
- maxsize=maxsize,
- block=block,
- ssl_context=ctx
- )
- session = requests.Session()
- session.mount("https://", SslAdapter())
- resp = session.get("https://old-server.com")
复制代码
这个方案专门用于对接老旧服务端,不建议对公网请求使用。
五、代理和抓包工具导致的SSLError
本地开启Fiddler、Charles等抓包工具抓取HTTPS流量时,这些工具会替换服务端证书,Python请求自然无法通过证书校验。
处理方式有两种:一是把抓包工具生成的根证书导入到certifi的ca.pem中,让Python信任该证书;二是调试阶段临时设置verify=False绕过。另外,检查环境变量中的HTTP_PROXY、HTTPS_PROXY,如果误设置了不可用的代理,同样会导致SSL握手失败:
- import os
- os.environ["HTTP_PROXY"] = ""
- os.environ["HTTPS_PROXY"] = ""
复制代码
清除代理后,再重新发起请求往往就能恢复正常。
六、线上排查步骤模板
遇到SSLError时,按以下顺序排查更高效:
1. 检查系统时间是否正确。时间偏差过大,证书会直接被判定过期。
2. 用浏览器访问目标URL,确认浏览器是否也报证书错误。如果浏览器正常,则问题出在Python客户端本地CA库;如果浏览器也报错,则是服务端证书本身有问题。
3. 升级certifi、requests、urllib3到最新版本,排除旧版本已知BUG。
4. 用verify=False做一次测试请求,判断网络链路是否畅通。
5. 如果verify=False正常,说明是证书校验问题。正式环境导入自签名CA证书并指定verify参数,调试环境可临时关闭校验。
6. 如果verify=False仍然报错,大概率是TLS版本不兼容、网络代理或防火墙拦截。打印完整堆栈,区分CERTIFICATE_VERIFY_FAILED和SSL_ERROR_UNSUPPORTED_VERSION两类错误,再针对性处理。
七、开发规范与避坑建议
生产代码中禁止全局设置verify=False。自签名内网服务优先使用指定CA证书文件的方式。
不要使用精简版Python,容易缺失OpenSSL与CA证书。容器化部署Python服务时,镜像务必安装ca-certificates包,例如Alpine系统:
- RUN apk add ca-certificates
复制代码
长连接场景下,建议复用同一个requests.Session和自定义SSL上下文,避免每次请求重新创建上下文,既提升性能,也减少握手开销。
出现SSLError时,不要无脑关闭校验,先定位根因。多数情况下,系统中维护一份正确的CA证书文件,就能解决大部分问题。
常见错误关键字速查:
- CERTIFICATE_VERIFY_FAILED:CA缺失或自签名证书。处理思路是升级certifi或指定CA pem文件。
- SSL_ERROR_UNSUPPORTED_VERSION:TLS版本不匹配。处理思路是自定义SSL上下文并开启兼容选项。
- SSL: CERTIFICATE_EXPIRED:证书过期。需要服务端更新证书,客户端无法单方面修复。
- hostname mismatch:证书域名与访问域名不一致。检查URL和证书的Common Name/SAN字段。
最后总结:SSLError绝大多数是由SSL证书校验失败引起,底层由OpenSSL抛出。verify=False只是绕过校验的调试手段,生产环境禁用。自签名证书优先传入CA根证书文件。certifi包维护Python内置CA根证书,升级certifi可以解决大部分公网证书链问题。内网老旧设备则考虑自定义SSL上下文做TLS版本兼容。代理抓包、系统时间错误、容器缺少ca-certificates也都是高频诱因,排查时按顺序检查即可。 |