Python 3.12中aiohttp禁用SSL验证失效的问题求助与解决方案记录
Python 3.12中aiohttp禁用SSL验证失效的问题求助与解决方案记录
最近把项目从Python 3.7.9升级到3.12.3后,遇到了一个头疼的问题:之前用aiohttp发送请求的代码突然跑不通了!
先给大家看看我的请求代码:
async with aiohttp.ClientSession(auth=aiohttp.BasicAuth(self.user, self.password), timeout=aiohttp.ClientTimeout(connect=self.timeouts[0], sock_read=self.timeouts[1])) as session: async with session.post(self.endpoint, data=query.encode('utf-8'), headers={'content-type' : 'application/xml'}, ssl=False) as response: response.raise_for_status() return await response.text()
情况是这样的:请求的目标服务器有SSL证书,但证书是“无效”的——这是我们必须对接的软件的问题,所以之前一直用ssl=False来跳过证书验证,在Python 3.7.9上完全没问题。
但升级到3.12.3之后,直接抛出了SSL握手失败的错误,完整报错信息如下:
Traceback (most recent call last): File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 1116, in _wrap_create_connection return await self._loop.create_connection(*args, **kwargs, sock=sock) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python312\Lib\asyncio\base_events.py", line 1149, in create_connection transport, protocol = await self._create_connection_transport( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python312\Lib\asyncio\base_events.py", line 1182, in _create_connection_transport await waiter File "C:\Program Files\Python312\Lib\asyncio\sslproto.py", line 578, in _on_handshake_complete raise handshake_exc File "C:\Program Files\Python312\Lib\asyncio\sslproto.py", line 560, in _do_handshake self._sslobj.do_handshake() File "C:\Program Files\Python312\Lib\ssl.py", line 917, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000) The above exception was the direct cause of the following exception: Traceback (most recent call last): File "c:\dev\my_project\server\controllers\rkeeper_cacher\update_task.py", line 38, in rkeeper_cacher_updater await _wait_with_raise(tasks) File "c:\dev\my_project\server\controllers\rkeeper_cacher\update_task.py", line 17, in _wait_with_raise future.result() File "c:\dev\my_project\server\controllers\rkeeper_cacher\service.py", line 124, in update_ref_if_need if cache_version == -1 or cache_version != await self._get_ref_version(args.rk_ref_name): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\server\controllers\rkeeper_cacher\service.py", line 58, in _get_ref_version xml = await self.rk_client.send( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\server\ext\rk7_api\async_client\client.py", line 35, in send return await self.send_command(command) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\server\ext\rk7_api\async_client\client.py", line 24, in send_command return await self.send_xml(command._serialize()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\server\ext\rk7_api\async_client\client.py", line 12, in send_xml result = await self.query_helper.try_send_str( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\server\ext\rk7_api\async_client\query_helpers.py", line 24, in try_send_str async with session.post( File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\client.py", line 1423, in __aenter__ self._resp: _RetType = await self._coro ^^^^^^^^^^^^^^^^ File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\client.py", line 701, in _request conn = await self._connector.connect( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 544, in connect proto = await self._create_connection(req, traces, timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 1050, in _create_connection _, proto = await self._create_direct_connection(req, traces, timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 1394, in _create_direct_connection raise last_exc File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 1363, in _create_direct_connection transp, proto = await self._wrap_create_connection( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\dev\my_project\venv3-12\Lib\site-packages\aiohttp\connector.py", line 1120, in _wrap_create_connection raise ClientConnectorSSLError(req.connection_key, exc) from exc Cannot connect to host 127.0.0.1:3015 ssl:<ssl.SSLContext object at 0x000001E811FEFED0> [[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]: aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host 127.0.0.1:3015 ssl:<ssl.SSLContext object at 0x000001E811FEFED0> [[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1000)]
一开始我以为是aiohttp的问题,试了好几种禁用SSL验证的方法都没用:
- 通过
trust_env=True配置; - 给
ClientSession创建自定义的ssl_context来跳过检查; - 把禁用检查的
SSLContext代替ssl=False传入请求。
我的环境版本:
- Python 3.12.3
- 相关依赖版本:
aiohappyeyeballs==2.4.4 aiohttp==3.11.10 aiosignal==1.3.1 attrs==24.2.0 frozenlist==1.5.0 idna==3.10 multidict==6.1.0 propcache==0.2.1 yarl==1.18.3
解决方案
最后才发现,根本原因是目标服务器用的是TLS 1.0协议,而Python 3.10之后默认已经不再支持这个老旧的协议了!
解决方法是创建一个允许TLS 1.0的SSLContext,同时关闭证书验证,代码如下:
import ssl # 创建SSL上下文,关闭证书验证 ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE # 启用TLS 1.0和TLS 1.1(根据服务器实际情况调整) ssl_context.set_ciphers('DEFAULT@SECLEVEL=1') ssl_context.options &= ~ssl.OP_NO_TLSv1_0 ssl_context.options &= ~ssl.OP_NO_TLSv1_1
然后把这个ssl_context传入请求的ssl参数,代替原来的ssl=False:
async with session.post(self.endpoint, data=query.encode('utf-8'), headers={'content-type' : 'application/xml'}, ssl=ssl_context) as response: # ... 后续逻辑
⚠️ 特别提醒:这个操作会大幅降低请求的安全性,只有在完全信任目标服务器、且没有其他替代方案的情况下才使用!
备注:内容来源于stack exchange,提问作者Nikita




