1. 项目概述为什么我们需要处理SSL证书警告在Python的网络编程世界里requests库几乎是每个开发者都会接触到的工具它简洁的API让HTTP请求变得像喝水一样简单。然而当你兴冲冲地写了一段代码去抓取某个HTTPS网站的数据或者调用一个内部测试环境的API时控制台突然蹦出一大串红色的InsecureRequestWarning甚至直接抛出一个SSLError那种感觉就像开车时仪表盘突然亮起了故障灯让人心头一紧。这个项目要解决的正是这个“故障灯”问题。简单来说就是在使用Python的requests库发起HTTPS请求时如何安全、有效地处理SSL证书验证相关的问题包括忽略自签名证书、过期证书的验证以及关闭由此产生的烦人警告。这绝不是教你“关闭安全功能去瞎搞”而是在特定、合理的场景下如开发测试、访问受信任的内部服务、处理已知安全的旧系统让程序能够顺畅运行的必要技巧。如果你正在开发爬虫、自动化测试脚本、内部系统集成工具或者仅仅是学习过程中被证书问题卡住那么接下来的内容就是为你准备的实战指南。2. SSL证书验证的核心原理与Requests的默认行为要解决问题得先明白问题从哪来。HTTPS协议中的“S”代表安全Secure其核心是SSL/TLS协议而证书验证是TLS握手过程中确认对方身份是否可信的关键一步。2.1 SSL/TLS握手与证书链验证当你用requests.get(‘https://example.com‘)时你的电脑客户端和服务器之间会进行一次复杂的“暗号对接”即TLS握手。服务器会出示它的SSL证书这个证书就像它的“数字身份证”。你的电脑不会轻易相信这张身份证它会做以下几件事验证签发者检查这张证书是由谁颁发的。它会沿着证书的颁发链向上追溯一直追到一个你电脑操作系统或Python环境里预先信任的“根证书颁发机构Root CA”。常见的如Let‘s Encrypt、DigiCert等。如果你的系统信任了这个根CA那么由它签发的证书就是可信的。验证域名检查证书上声明的“使用者可选名称SAN”或“通用名称CN”是否与你正在访问的域名example.com完全匹配。如果不匹配就会报“主机名不匹配”错误。验证有效期检查证书是否在它声明的“生效日期”和“过期日期”之内。访问一个证书过期的网站就像使用一张过期的身份证会被视为无效。验证吊销状态理论上客户端还会通过OCSP或CRL协议查询证书是否已被颁发机构吊销。不过在实际的requests库默认验证中这一步有时不是强制性的。requests库底层依赖于urllib3而urllib3又依赖于操作系统提供的OpenSSL库或certifi这个Python包来获取受信任的根证书列表。默认情况下requests的verify参数为True意味着它会严格执行上述验证流程。任何一环出错都会抛出requests.exceptions.SSLError异常。2.2 警告信息的来源urllib3与InsecureRequestWarning即使SSL验证失败了有时程序可能不会直接崩溃而是会先收到一个警告。这个警告来源于urllib3。当urllib3检测到一些潜在的安全风险但又不足以或未被配置为直接阻断请求时它会通过Python的warnings模块发出InsecureRequestWarning。最常见的触发条件就是关闭了SSL验证verifyFalse。这个警告的本意是好的提醒开发者“嘿你正在进行的网络通信可能不安全”。但在自动化脚本、测试环境或后台服务中这些警告信息会污染日志干扰正常输出甚至可能被监控系统误判为错误。注意将verify设置为False会完全禁用服务器证书的验证。这意味着你的连接容易受到中间人攻击攻击者可以窃听或篡改你与服务器之间的数据。绝对不要在对安全性有要求的生产环境、处理敏感数据如密码、支付信息或访问不信任的公开网站时使用。它的适用场景仅限于开发、测试或访问你完全可控且信任的内部服务。3. 核心操作忽略证书验证与关闭警告的四种方法理解了原理我们来看具体怎么做。我将从最直接粗暴的方法讲到更优雅、可控的方法。3.1 方法一单次请求忽略验证verifyFalse这是最常用、最直接的方法。在发起请求时将verify参数设置为False。import requests # 访问一个使用自签名证书的内部测试站点 url “https://internal-test-api.company.com/data“ response requests.get(url, verifyFalse) print(response.status_code)执行这段代码你很可能看到如下警告/usr/local/lib/python3.9/site-packages/urllib3/connectionpool.py:1045: InsecureRequestWarning: Unverified HTTPS request is being made to host ‘internal-test-api.company.com‘. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings warnings.warn(请求成功了但警告出现了。这只是一个警告不会阻止程序运行但很碍眼。为什么有效verifyFalse告诉urllib3跳过对服务器证书的所有验证步骤。它不再检查签发者、有效期和域名。请求会继续执行但通信失去了SSL证书提供的身份保证。3.2 方法二全局忽略警告urllib3.disable_warnings如果你的整个脚本或项目都需要访问一个特定的测试环境不想在每个请求里都看到警告可以全局关闭urllib3发出的特定警告。import requests import urllib3 # 强烈建议只禁用 InsecureRequestWarning避免掩盖其他潜在问题 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 现在使用 verifyFalse 的请求将不再打印警告 url “https://internal-test-api.company.com/data“ response requests.get(url, verifyFalse) print(“请求成功且无警告“, response.status_code)实操心得urllib3.disable_warnings()默认会禁用所有来自urllib3的警告这可能会让你错过其他重要提示。最佳实践是像上面一样明确指定要禁用的警告类别urllib3.exceptions.InsecureRequestWarning。这个设置是进程全局的。一旦执行当前Python进程内所有后续的requests请求只要触发同类警告都不会再显示。它不影响其他库产生的警告。适合场景在测试脚本、CI/CD流水线中为了让日志输出干净整洁。3.3 方法三使用自定义CA证书包verify‘/path/to/cert.pem‘这是比完全忽略验证更安全、更专业的做法。适用于访问使用内部CA证书颁发机构签发证书的服务或者你信任的某个特定自签名证书。步骤获取证书从服务器管理员那里获取其SSL证书通常是.crt或.pem文件或者从浏览器导出该站点的证书。在请求中指定将verify参数指向这个证书文件。import requests # 假设你有一个内部CA的证书文件 internal_ca_cert ‘/path/to/your/company-internal-ca.pem‘ url “https://internal.company.com/api“ # 使用自定义CA包进行验证 response requests.get(url, verifyinternal_ca_cert) print(response.status_code)为什么更安全你没有关闭验证而是替换了验证所依据的“信任名单”。你的程序只信任你指定的这个CA颁发的证书。如果遇到中间人攻击攻击者使用的证书如果不是由你这个内部CA签发的连接依然会被拒绝。这就在便利性和安全性之间取得了很好的平衡。常见问题证书格式requests/urllib3通常支持PEM格式的证书。如果你拿到的是.cer或.der可能需要用OpenSSL命令转换。证书链有时服务器提供的证书是一个链服务器证书中间CA证书。你需要确保你的证书文件包含完整的证书链或者将根CA证书和中间CA证书合并到一个文件中。3.4 方法四通过环境变量或Session全局配置对于需要统一配置的大型项目可以通过requests.Session对象或环境变量来管理。使用Session对象import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 创建一个会话并设置全局参数 session requests.Session() session.verify False # 该会话发起的**所有**请求都将忽略验证 # 或者 session.verify ‘/path/to/cert.pem‘ # 使用会话发起请求 response session.get(‘https://internal-test-api.company.com/data‘) response2 session.post(‘https://internal-test-api.company.com/update‘, json{...})使用Session的好处是可以复用TCP连接提升性能并且统一管理请求头、Cookies、认证等信息SSL验证策略只是其中之一。通过环境变量不推荐用于动态控制requests库会读取一个名为REQUESTS_CA_BUNDLE的环境变量。如果设置了这个变量库会将其值作为默认的CA证书包路径。这更多用于系统级别的配置比如在某个Docker容器内全局指定CA证书。# 在命令行中设置 export REQUESTS_CA_BUNDLE/etc/ssl/certs/custom-ca-bundle.crt# 然后在Python代码中requests.get() 如果不指定verify就会使用上面的路径 import requests response requests.get(‘https://internal.company.com‘) # 会自动使用环境变量指定的证书包4. 深入排查当verifyFalse仍报错时怎么办有时候即使你设置了verifyFalse仍然会遇到连接错误比如SSLError: HTTPSConnectionPool... Max retries exceeded或者SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]。这通常意味着问题超出了证书验证本身进入了更底层的SSL/TLS协议协商层面。4.1 检查协议和密码套件兼容性老旧的服务器可能只支持过时的TLS 1.0或1.1协议而现代Python环境如3.10的OpenSSL可能默认已禁用这些不安全的协议。同样服务器支持的加密套件可能与客户端不匹配。解决方案尝试指定协议版本谨慎使用import requests import ssl from urllib3.poolmanager import PoolManager from requests.adapters import HTTPAdapter class CustomHTTPAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): # 创建一个使用特定SSL上下文的PoolManager context ssl.create_default_context() # 降低安全级别以兼容老旧服务器生产环境慎用 context.minimum_version ssl.TLSVersion.TLSv1 # 允许TLS 1.0及以上 # context.set_ciphers(‘DEFAULTSECLEVEL1‘) # 有时也需要降低密码套件安全等级 kwargs[‘ssl_context‘] context return super().init_poolmanager(*args, **kwargs) session requests.Session() adapter CustomHTTPAdapter() session.mount(‘https://‘, adapter) session.verify False try: response session.get(‘https://very-old-system.internal‘) print(response.status_code) except Exception as e: print(f“连接失败 {e}“)警告强制使用低版本TLS如TLSv1.0, TLSv1.1或弱密码套件会严重降低通信安全性仅应在绝对必要且网络环境隔离如纯内网的情况下用于访问无法升级的遗留系统。4.2 处理证书链不完整有些服务器配置不当发送的证书链不完整缺少中间CA证书。这会导致客户端无法构建一条通往可信根CA的完整路径即使你忽略了主机名验证也可能在底层握手失败。解决方案除了设置verifyFalse你可能需要像上面一样通过自定义SSLContext来更彻底地禁用验证。import requests import ssl class InsecureHTTPAdapter(requests.adapters.HTTPAdapter): def init_poolmanager(self, *args, **kwargs): ctx ssl.create_default_context() ctx.check_hostname False # 禁用主机名检查 ctx.verify_mode ssl.CERT_NONE # 禁用所有证书验证 kwargs[‘ssl_context‘] ctx return super().init_poolmanager(*args, **kwargs) session requests.Session() session.mount(‘https://‘, InsecureHTTPAdapter()) # 注意这里不再需要 session.verify False因为适配器已经处理了 response session.get(‘https://badly-configured-server.com‘)这种方法比单纯的verifyFalse更“强力”因为它从SSL上下文层面禁用了验证。4.3 网络与代理问题错误信息有时会误导人。SSLError也可能是因为网络不通、代理服务器配置错误或防火墙拦截导致的。verifyFalse只解决证书问题不解决网络层问题。排查步骤先用浏览器或curl测试curl -k https://your-target-url-k是curl里忽略证书验证的选项。如果curl也失败那很可能不是Python或证书的问题。检查代理如果你的环境使用HTTP代理确保requests正确配置了代理。proxies { ‘http‘: ‘http://your-proxy:port‘, ‘https‘: ‘http://your-proxy:port‘, # 注意很多HTTPS代理也使用HTTP协议 } response requests.get(url, verifyFalse, proxiesproxies)超时设置给请求加上合理的超时参数避免因网络延迟导致程序挂起。response requests.get(url, verifyFalse, timeout(3.05, 27)) # (连接超时 读取超时)5. 实战场景与最佳实践指南掌握了各种方法我们来看看如何在不同的真实场景中应用它们并遵循安全最佳实践。5.1 场景一开发与测试环境特点使用自签名证书的本地服务如Docker容器内的服务、本地搭建的测试服务器。推荐方案首选为测试环境生成一个自签名证书并将其添加到你的操作系统或Python环境的信任库中。对于macOS/Linux可以将.pem文件放到/usr/local/share/ca-certificates/并运行update-ca-certificates。这样所有程序包括requests都会信任它无需任何代码修改。次选在测试脚本中使用verifyFalse并配合urllib3.disable_warnings。为了方便可以写一个工具函数或配置类来管理。import requests import urllib3 from functools import wraps def insecure_session(): “”“创建一个忽略SSL验证并禁用警告的会话”“” session requests.Session() session.verify False # 仅为本会话禁用警告更局部化 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) return session # 使用 with insecure_session() as s: resp s.get(‘https://localhost:8443/api‘)5.2 场景二企业内部系统集成特点访问公司内网服务这些服务使用由内部CA统一签发的证书。推荐方案绝对不要使用verifyFalse。应获取内部CA的根证书并将其用于验证。方法A项目级将内部CA证书文件放在项目目录下如certs/在代码中指定路径。import os import requests BASE_DIR os.path.dirname(os.path.abspath(__file__)) CA_CERT_PATH os.path.join(BASE_DIR, ‘certs‘, ‘company-root-ca.pem‘) session requests.Session() session.verify CA_CERT_PATH方法B系统级将内部CA证书安装到运行服务的服务器或Docker镜像的系统信任库中。这是最彻底、最规范的做法。5.3 场景三Web爬虫与数据采集特点需要处理大量不同域名其中一些可能证书配置不当如过期、域名不匹配。推荐方案评估风险如果采集的是公开、非敏感信息且你确信目标网站就是它本身非中间人攻击短期使用verifyFalse可能是务实的。但必须清楚风险。设置超时和重试配合verifyFalse一定要设置合理的超时和重试逻辑因为这类网站可能本身就不稳定。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() session.verify False # 配置重试策略 retry_strategy Retry( total3, # 总重试次数 backoff_factor1, # 重试等待时间因子 status_forcelist[429, 500, 502, 503, 504], # 遇到这些状态码重试 allowed_methods[“GET“, “POST“] # 只对GET/POST方法重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(“http://“, adapter) session.mount(“https://“, adapter) # 发起请求 try: response session.get(‘https://some-site.com‘, timeout10) except requests.exceptions.SSLError as e: print(f“SSL错误可能是证书问题{e}“) except requests.exceptions.RequestException as e: print(f“请求失败{e}“)考虑使用CERT_NONE模式对于极其顽固的网站如果verifyFalse仍不行可以考虑使用第4.2节中自定义InsecureHTTPAdapter的方法。5.4 安全红线与注意事项总结生产环境禁用除非有极其特殊且经过安全评估的理由否则严禁在生产环境代码中使用verifyFalse或禁用主机名验证。敏感数据任何涉及登录凭证、个人信息、支付数据的请求必须进行完整的证书验证。公开网络在咖啡厅、机场等公共Wi-Fi下中间人攻击风险极高务必确保SSL验证开启。依赖传递如果你在编写一个供他人使用的库或工具不要在内部硬编码verifyFalse。应该通过参数让调用者来决定并在文档中明确说明风险。日志与监控如果在测试环境使用了忽略验证确保在日志中有明确的记录并且监控系统能区分这种“预期内的不安全连接”和真正的安全事件。证书管理对于内部系统积极推动使用正规的、由可信内部CA或公共CA签发的证书从根本上避免这个问题。6. 高级话题自定义SSL上下文与适配器对于有更复杂需求的场景比如需要双向TLS认证客户端也需要证书、使用特定密码套件或调整其他底层SSL参数就需要深入到SSLContext和自定义适配器。6.1 客户端证书认证双向TLS有些安全的API要求客户端不仅验证服务器服务器也要验证客户端这就需要提供客户端证书和私钥。import requests # 指定客户端证书.crt或.pem和私钥.key client_cert (‘/path/to/client.cert‘, ‘/path/to/client.key‘) # 服务器证书可能还是需要验证这里用自定义CA或False server_ca_cert ‘/path/to/server-ca.pem‘ response requests.get( ‘https://secure-api.internal.com‘, certclient_cert, verifyserver_ca_cert # 或 True如果服务器证书是公共CA签的 )注意事项私钥文件必须保密绝不能提交到代码仓库。通常通过环境变量或配置管理系统来传递文件路径。6.2 创建可复用的安全/非安全会话工厂为了代码整洁和安全可以创建工厂函数来生成配置好的会话对象。import requests import ssl from urllib3.poolmanager import PoolManager from requests.adapters import HTTPAdapter from functools import lru_cache def create_secure_session(ca_bundle_pathNone, client_cert_pathNone, client_key_pathNone): “”“创建一个进行完整SSL验证的安全会话”“” session requests.Session() if ca_bundle_path: session.verify ca_bundle_path else: session.verify True # 使用系统默认信任库 if client_cert_path and client_key_path: session.cert (client_cert_path, client_key_path) return session def create_insecure_test_session(disable_warningTrue): “”“创建一个用于测试的非安全会话忽略验证”“” import urllib3 if disable_warning: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class _InsecureAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): ctx ssl.create_default_context() ctx.check_hostname False ctx.verify_mode ssl.CERT_NONE kwargs[‘ssl_context‘] ctx return super().init_poolmanager(*args, **kwargs) session requests.Session() insecure_adapter _InsecureAdapter() session.mount(‘https://‘, insecure_adapter) # 注意这里不再需要设置 session.verify return session # 使用示例 secure_session create_secure_session(ca_bundle_path‘./certs/internal-ca.pem‘) insecure_session create_insecure_test_session() # 在测试代码中明确使用不安全会话 # 在生产代码中明确使用安全会话这种模式将配置逻辑封装起来使主业务代码更清晰也更容易在不同环境开发、测试、生产间切换。7. 常见问题速查与排错实录在实际操作中你可能会遇到一些意想不到的问题。这里记录了几个典型案例和排查思路。问题1设置了verifyFalse但还是报SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)可能原因你遇到的不是服务器证书验证失败而是客户端证书如果你配置了cert参数或代理服务器证书验证失败。排查检查你的请求是否配置了cert参数。如果有确保客户端证书和私钥是有效的、配对的并且格式正确PEM格式通用性最好。如果你在使用代理可能是代理服务器的证书不被信任。尝试在不使用代理的情况下直接连接目标如果网络允许或者获取代理的CA证书并指定verify参数为其路径。极少数情况下系统的OpenSSL库或certifi包可能损坏。可以尝试更新certifipip install --upgrade certifi。问题2警告是没了但程序好像变慢了或者偶尔会卡住。可能原因禁用SSL验证有时会影响urllib3的连接池和重试机制或者与代理服务器交互时出现意外。更常见的是目标服务器本身响应慢或网络不稳定。排查首要检查是否为请求添加了timeout参数没有超时设置的网络请求是危险的会一直等待。使用工具如curl -w ‘%{time_total}‘ -k https://...或浏览器开发者工具的网络面板测试目标服务器的原始响应速度排除服务器端问题。尝试创建一个新的、干净的Session对象避免之前会话状态的影响。问题3在Docker容器内运行脚本证书错误。可能原因Docker镜像特别是基于Alpine等精简镜像可能没有包含完整的CA证书包或者certifi包找不到它的证书文件。解决方案在Dockerfile中安装CA证书包。对于Alpine镜像RUN apk add --no-cache ca-certificates。对于Debian/Ubuntu镜像RUN apt-get update apt-get install -y ca-certificates。确保certifi已安装并可以明确指定其证书路径虽然requests通常会自己找到。import certifi import requests response requests.get(‘https://example.com‘, verifycertifi.where())如果访问的是内部服务需要将内部CA证书复制到镜像中并更新系统的信任库或像之前一样在代码中指定路径。问题4如何临时为单个脚本禁用所有警告包括非SSL的虽然不推荐但在某些调试场景下可能需要。可以通过Python的warnings模块全局过滤。import warnings warnings.filterwarnings(‘ignore‘) # 忽略所有警告 import requests response requests.get(‘https://internal‘, verifyFalse) # 不会有任何警告输出但也会隐藏其他有用警告慎用我个人在实际项目中的体会是处理SSL证书问题就像处理交通规则。在封闭的测试场地内网测试环境你可以根据需要调整规则但一旦上路生产环境、公网就必须严格遵守否则后果严重。最根本的解决之道是推动服务端使用正确配置的、由可信CA签发的证书。代码层面的绕过方案始终是权宜之计使用时务必心怀敬畏明确边界。最后一个小技巧在团队项目中关于是否忽略SSL验证的决策最好通过配置文件或环境变量如REQUESTS_VERIFYfalse来控制而不是硬编码在逻辑里这样更容易在不同部署环境间进行切换和管理。