當(dāng)python `requests`庫(kù)在進(jìn)行https連接時(shí),如果客戶端的tls版本過(guò)低(如tlsv1.0),可能導(dǎo)致服務(wù)器拒絕連接并拋出`connectionreseterror`。本文將深入探討此問(wèn)題的原因,并提供一套完整的解決方案,包括如何強(qiáng)制指定tls協(xié)議版本、配置加密套件(cipher suites)以及正確處理ssl證書(shū)驗(yàn)證,以確保python應(yīng)用程序能夠與現(xiàn)代安全標(biāo)準(zhǔn)的服務(wù)器建立穩(wěn)定且安全的連接。
在進(jìn)行HTTPS通信時(shí),客戶端和服務(wù)器之間需要通過(guò)TLS(Transport Layer Security)協(xié)議進(jìn)行握手,協(xié)商加密算法和會(huì)話密鑰。如果客戶端在Client Hello消息中提議的TLS版本過(guò)舊,而服務(wù)器已禁用該版本(例如,為了滿足PCI DSS等安全標(biāo)準(zhǔn),許多服務(wù)器不再支持TLSv1.0和TLSv1.1),則服務(wù)器會(huì)直接拒絕連接,導(dǎo)致客戶端收到ConnectionResetError。
常見(jiàn)問(wèn)題現(xiàn)象如下:
即使Python環(huán)境中的OpenSSL庫(kù)是最新版本(例如OpenSSL 1.1.1w),Python的ssl模塊或requests庫(kù)的默認(rèn)行為有時(shí)仍可能導(dǎo)致使用較舊的TLS版本。這通常需要我們顯式地配置SSLContext來(lái)強(qiáng)制使用更安全的TLS協(xié)議。
在嘗試修復(fù)客戶端配置之前,了解目標(biāo)服務(wù)器支持的TLS版本和加密套件至關(guān)重要??梢允褂胦penssl s_client命令進(jìn)行診斷:
立即學(xué)習(xí)“Python免費(fèi)學(xué)習(xí)筆記(深入)”;
openssl s_client -connect www.handlingandfulfilment.co.uk:443
或者,如果服務(wù)運(yùn)行在非標(biāo)準(zhǔn)端口:
openssl s_client -connect www.handlingandfulfilment.co.uk:8079
該命令的輸出會(huì)顯示成功建立連接時(shí)使用的TLS版本和加密套件,例如:
New, TLSv1.2, Cipher is AES256-GCM-SHA384
這表明服務(wù)器支持TLSv1.2,并且AES256-GCM-SHA384是一個(gè)可用的加密套件。這些信息將指導(dǎo)我們配置Python客戶端。
Python的ssl模塊允許我們創(chuàng)建自定義的SSLContext,從而精細(xì)控制TLS握手行為。
通過(guò)設(shè)置SSLContext的options屬性,可以禁用TLSv1.0和TLSv1.1:
import ssl context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) # 禁用TLS 1.0 和 TLS 1.1 context.options |= ssl.OP_NO_TLSv1 context.options |= ssl.OP_NO_TLSv1_1 # 對(duì)于更現(xiàn)代的應(yīng)用,也可以禁用TLS 1.2,強(qiáng)制TLS 1.3 # context.options |= ssl.OP_NO_TLSv1_2
使用ssl.PROTOCOL_TLS_CLIENT是創(chuàng)建客戶端SSLContext的推薦方式,它會(huì)自動(dòng)設(shè)置一些合理的默認(rèn)值。
根據(jù)openssl s_client的診斷結(jié)果,我們可以使用set_ciphers方法來(lái)指定允許的加密套件。例如,如果服務(wù)器使用AES256-GCM-SHA384:
CIPHERS = 'ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384:!aNULL:!MD5' context.set_ciphers(CIPHERS)
這里的!aNULL:!MD5是常見(jiàn)的做法,用于排除不安全的匿名加密套件和使用MD5哈希的套件。
在配置TLS版本和加密套件后,可能會(huì)遇到證書(shū)驗(yàn)證失敗的問(wèn)題。這是因?yàn)榭蛻舳诵枰粋€(gè)信任的根證書(shū)頒發(fā)機(jī)構(gòu)(CA)列表來(lái)驗(yàn)證服務(wù)器提供的證書(shū)。certifi庫(kù)提供了一個(gè)最新的、經(jīng)過(guò)Mozilla維護(hù)的CA證書(shū)包,可以很方便地集成到SSLContext中:
import certifi context.load_verify_locations(certifi.where())
對(duì)于使用requests庫(kù)(或其上層庫(kù)如zeep)的應(yīng)用程序,最優(yōu)雅的解決方案是創(chuàng)建一個(gè)自定義的HTTPAdapter。
import certifi import requests from requests.adapters import HTTPAdapter, Retry from urllib3 import PoolManager from urllib3.util.ssl_ import create_urllib3_context from zeep import Client from zeep.transports import Transport from dataclasses import dataclass, field # 定義所需的加密套件 CIPHERS = 'ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384:!aNULL:!MD5' class TLSAdapter(HTTPAdapter): """ 自定義HTTPAdapter,用于強(qiáng)制TLS版本、指定加密套件和加載certifi證書(shū)。 """ def init_poolmanager(self, *args, **kwargs): # 創(chuàng)建一個(gè)帶有指定加密套件的SSL上下文 context = create_urllib3_context(ciphers=CIPHERS) # 加載certifi提供的CA證書(shū) context.load_verify_locations(certifi.where()) # 再次設(shè)置加密套件,確保生效 context.set_ciphers(CIPHERS) # 禁用TLS 1.0 和 TLS 1.1 # SSL_OP_NO_TLSv1 (0x80000) 和 SSL_OP_NO_TLSv1_1 (0x1000000) 是OpenSSL的選項(xiàng)標(biāo)志 context.options |= 0x80000 # ssl.OP_NO_TLSv1 context.options |= 0x1000000 # ssl.OP_NO_TLSv1_1 # 將自定義的SSL上下文傳遞給urllib3的PoolManager self.poolmanager = PoolManager(*args, ssl_context=context, **kwargs) def requests_retry_session( retries=8, backoff_factor=0.3, status_forcelist=(500, 502, 503, 504), session=None, ) -> requests.Session: """ 創(chuàng)建一個(gè)帶有重試機(jī)制和自定義TLS配置的requests會(huì)話。 """ session = session or requests.Session() retry = Retry( total=retries, read=retries, connect=retries, backoff_factor=backoff_factor, status_forcelist=status_forcelist, ) # 使用自定義的TLSAdapter掛載到http和https協(xié)議上 adapter = TLSAdapter(max_retries=retry) session.mount('http://', adapter) session.mount('https://', adapter) return session # 示例:如何將此會(huì)話用于zeep客戶端 @dataclass(order=True) class ArkH: wsdl_url: str consumerName: str passCode: str helixClientName: str helixUsername: str userPassword: str client: Client = field(init=False) dummyCustomer: str dummy_customer_mapping: dict = field(default_factory=lambda: {'CTS':'CTS'}) dear_warehouse: str dear_ship_after_3PL_shipment_date: bool def __post_init__(self): # 創(chuàng)建一個(gè)帶有自定義TLS配置和重試機(jī)制的requests會(huì)話 session = requests_retry_session() # 將此會(huì)話傳遞給Zeep的Transport transport = Transport(session=session, timeout=40, operation_timeout=40) self.client = Client(self.wsdl_url, transport=transport) # 實(shí)際使用示例 (假設(shè)你有一個(gè)WSDL URL) # ark_client = ArkH( # wsdl_url='https://www.handlingandfulfilment.co.uk:8079/YourService?wsdl', # consumerName='your_consumer', # passCode='your_passcode', # helixClientName='your_helix_client', # helixUsername='your_helix_username', # userPassword='your_user_password', # dummyCustomer='CTS', # dear_warehouse='main', # dear_ship_after_3PL_shipment_date=True # ) # print("Zeep client initialized with custom TLS settings.")
import logging logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True
解決Python requests或zeep連接中因TLS版本過(guò)舊導(dǎo)致的ConnectionResetError,關(guān)鍵在于顯式地配置SSLContext。這包括禁用不安全的TLS協(xié)議版本(如TLSv1.0和TLSv1.1)、指定服務(wù)器支持的加密套件,以及利用certifi庫(kù)確保正確的證書(shū)驗(yàn)證。通過(guò)創(chuàng)建自定義的HTTPAdapter并將其掛載到requests.Session上,可以優(yōu)雅且一致地將這些安全配置應(yīng)用到整個(gè)應(yīng)用程序的HTTP/HTTPS請(qǐng)求中,從而提高連接的穩(wěn)定性和安全性。
以上就是解決Python Requests TLSv1握手失敗與服務(wù)器拒絕連接問(wèn)題的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!
每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)