一、问题现象与背景
当开发者使用requests.send()或相关方法发起HTTPS请求时,常会遇到类似以下的报错:
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: /api (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)')))
这种错误在访问自签名证书网站、过期证书或配置不规范的服务器时尤为常见。根据Cloudflare统计,约15%的API请求故障源于SSL/TLS证书问题。
二、根本原因分析
- 证书链不完整:服务器未提供完整的中间证书链
- CA根证书过期:如2020年Let's Encrypt根证书过期事件
- 主机名不匹配:证书Subject Alternative Name(SAN)与请求域名不符
- 系统证书库缺失:Python使用的OpenSSL无法找到信任锚
- 时间不同步:客户端系统时间与证书有效期不匹配
三、六种解决方案对比
| 方法 | 代码示例 | 安全性 | 适用场景 |
|---|---|---|---|
| 禁用验证(不推荐) | requests.get(url, verify=False) |
❌ 高风险 | 临时测试环境 |
| 自定义CA包 | requests.get(url, verify='/path/to/cacert.pem') |
✅ 安全 | 企业内网环境 |
| 更新证书库 | pip install certifi |
✅ 安全 | 证书库过时 |
| 添加证书到系统 | export SSL_CERT_FILE=/path/to/cert |
✅ 安全 | Linux服务器 |
| 使用会话配置 | session = requests.Session() session.verify = False |
❌ 高风险 | 批量请求场景 |
| 证书捆绑方案 | openssl s_client -showcerts -connect host:port |
✅ 安全 | 自签名证书 |
四、生产环境最佳实践
对于关键业务系统,建议采用分层验证策略:
- 使用
certifi.where()定位当前证书库路径 - 通过
openssl命令导出服务器证书链:
openssl s_client -connect example.com:443 -showcerts < /dev/null - 将证书合并到PEM文件,包含:
- 服务器证书
- 中间证书
- 根证书 - 在代码中指定验证路径:
DEFAULT_CA_BUNDLE = os.path.join(os.path.dirname(__file__), 'custom_ca.pem') requests.get(url, verify=DEFAULT_CA_BUNDLE)
五、进阶调试技巧
当标准解决方案无效时,可尝试:
- 使用
urllib3的详细日志:
import logging logging.basicConfig(level=logging.DEBUG)
- 检查证书有效期:
openssl x509 -in certificate.crt -noout -dates - 验证OCSP装订状态:
openssl s_client -connect example.com:443 -status
根据Mozilla TLS Observatory数据,正确配置的证书验证可将中间人攻击风险降低92%,因此切勿长期使用verify=False方案。