使用python的过程中,在发送网络请求时有时候会遇到如下问题:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
这个问题产生原因是python发送请求的网站或地址是https,这时需要验证对方网站的证书有效性。 但python使用的证书存储位置里找不到该网站的证书。
在任何发送http请求的地方都可能遇到,包括python内置库: ssl, urllib3, 以及一些第三方库:requests, httpx, aiohttp, 甚至包管理工具: pip, poetry 等。
certifi: 这是一个第三方模块,这个模块有一个证书存储。 使用pip命令对这个模块更新时,就等同于更新了最新的证书集合。很多其他模块都依赖于certifi模块。这个模块一般也会随其他模块安装时被一起安装,比如requests模块。
certifi.where()
调用这个方法将返回certifi保存的根证书的存储路径。
pip命令工具:这是安装python就自带的,甚至创建虚拟环境也会有独立的pip工具。每个pip工具都内置了一个certifi,注意,pip内置的certifi虽然与单独安装的certifi模块功能一样,但其只被pip命令工具自己用。其内置的证书存储也只能在pip工具自已更新时才更新 python -m pip install --upgrade pip
。 pip命令本身可能通过--cert
truststore: 第三方模块,用于将操作系统的证书存储位置暴露出来。 pip工具从v24.2版本开始内置了这个模块,使用pip命令可以同时使用内置的certifi以及操作系统的证书存储。通过其truststore.inject_into_ssl()方法,可以让那些原本使用certifi证书存储的第三方库转而使用操作系统的证书存储。
可以使用truststore.SSLContext()方法达到更细粒度的控制,比如对单个请求。truststore.inject_into_ssl()方法并不适合用于构建package,因为一旦这样的package被import到其他项目时,项目运行的整个环境里原本该依赖certifi证书存储的程序部分就都会转而使用操作系统的证书存储了。 所以,应该更多考虑细粒度的控制。全局的修改适合单个项目或独立运行的脚本,而不是用于共享的模块。
import ssl
import urllib3
import truststorectx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)http = urllib3.PoolManager(ssl_context=ctx)
resp = http.request("GET", "https://example.com")