如何解决 InsecureRequestWarning: Unverified HTTPS request is being made to host ‘47.113.219.226‘. Adding
如何解决 InsecureRequestWarning: Unverified HTTPS request is being made to host \'47.113.219.226\'. Adding certificate verification is strongly advised.
问题(全网解决方案大全)
目录
-
问题描述
-
警告产生的原因与背景
-
不推荐的“临时”解决办法
- 3.1. 直接禁止警告输出
- 3.2. 在单次请求中关闭验证(
verify=False
)
-
推荐的“根本”解决办法:证书校验与信任链
- 4.1. 使用操作系统/Python 自带的信任根存储
- 4.2. 使用
certifi
库维护信任根 - 4.3. 自定义并指定 CA 证书文件
- 4.4. 针对自签名/私有 CA 的证书配置
- 4.5. 将证书打包到可执行文件或 Docker 镜像中
-
多种编程语言/框架下的同类解决思路
- 5.1.
curl
命令行下的 TLS 验证 - 5.2. Node.js(
axios
、request
)下的类似警告 - 5.3. Java(
HttpClient
、OkHttp
)下的 TLS 验证配置 - 5.4. Go 语言下的
http.Client
TLS 配置 - 5.5. .NET(
HttpClient
)下的 Server Certificate 验证
- 5.1.
-
对比与实践:如何从开发到生产确保 HTTPS 安全
- 6.1. 在本地/测试环境中调试时的最佳实践
- 6.2. 在 Staging/预发布环境中引入 CA
- 6.3. 生产环境中的 TLS 证书申请与自动续期流程
-
常见故障排查方法与排错技巧
- 7.1. 使用
openssl s_client
或openssl x509
验证服务器证书 - 7.2. 检查中间证书链是否完整
- 7.3. 跨平台时区/系统证书更新导致的问题
- 7.4. 证书过期、域名/IP 不匹配等常见错误
- 7.1. 使用
-
总结与建议
-
附录:参考链接与资料汇总
问题描述
在使用 Python 的 requests
库(或其底层依赖 urllib3
)发起 HTTPS 请求时,若没有对目标主机的 TLS 证书进行校验,就会出现类似如下的警告(Warning):
InsecureRequestWarning: Unverified HTTPS request is being made to host \'47.113.219.226\'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
它的含义是:你正在向 https://47.113.219.226
发起一个不经过证书验证的 HTTPS 请求。如果对方使用的是自签名证书、或者根本不存在可信的 TLS 证书,就会触发这一警告。简单来说,这是在提醒你:
- 你的客户端并未验证服务器证书的合法性,存在中间人攻击的风险
- 建议你在发起请求时启用证书校验机制,以防止安全隐患
这篇技术博客将会“超详细”地汇集来自全网的各种解决方案,从“最急就章”的临时抑制到“根本治本”的正规证书校验,力求读者在开发、测试、生产不同场景下都能找到最合适、最全面的处理方法。
作者简介
猫头虎是谁?
大家好,我是 猫头虎,猫头虎技术团队创始人,也被大家称为猫哥。我目前是COC北京城市开发者社区主理人、COC西安城市开发者社区主理人,以及云原生开发者社区主理人,在多个技术领域如云原生、前端、后端、运维和AI都具备丰富经验。
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用方法、前沿科技资讯、产品评测、产品使用体验,以及产品优缺点分析、横向对比、技术沙龙参会体验等。我的分享聚焦于云服务产品评测、AI产品对比、开发板性能测试和技术报告。
目前,我活跃在CSDN、51CTO、腾讯云、阿里云开发者社区、知乎、微信公众号、视频号、抖音、B站、小红书等平台,全网粉丝已超过30万。我所有平台的IP名称统一为猫头虎或猫头虎技术团队。
我希望通过我的分享,帮助大家更好地掌握和使用各种技术产品,提升开发效率与体验。
作者名片 ✍️
- 博主:猫头虎
- 全网搜索关键词:猫头虎
- 作者微信号:Libin9iOak
- 作者公众号:猫头虎技术团队
- 更新日期:2025年06月04日
- 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能!
加入我们AI共创团队 🌐
- 猫头虎AI共创社群矩阵列表:
- 点我进入共创社群矩阵入口
- 点我进入新矩阵备用链接入口
加入猫头虎的共创圈,一起探索编程世界的无限可能! 🚀
部分专栏链接
:
🔗 精选专栏:
- 《面试题大全》 — 面试准备的宝典!
- 《IDEA开发秘籍》 — 提升你的IDEA技能!
- 《100天精通鸿蒙》 — 从Web/安卓到鸿蒙大师!
- 《100天精通Golang(基础入门篇)》 — 踏入Go语言世界的第一步!
正文
警告产生的原因与背景
-
HTTPS 的本质:
- HTTPS(HTTP over TLS/SSL)在传输层通过加密与身份验证保证通信的机密性与完整性。
- 其中“验证”(Authentication)就要求客户端验证服务器所呈现的证书是否由受信任的 CA 签发、是否未过期、是否与域名/IP 匹配,以及证书链是否完整。
-
为何会出现
InsecureRequestWarning
:requests
库(或底层的urllib3
)默认会尝试用certifi
提供的 CA 根证书来验证对方证书。- 如果服务器使用自签名证书,或者根本没有提供相应的可信 CA 签发的证书,那么
requests
会因为“证书校验失败”而报错/抛出异常。 - 为了“方便开发测试”,
requests
支持一个参数verify=False
,用于跳过证书验证。但同时也会触发InsecureRequestWarning
警告,提醒你“这很不安全”。
-
风险:中间人攻击(MITM)
- 如果不验证对方身份,攻击者可以在你与服务器之间插入一个恶意中间人,劫持明文流量或重定向到别处。
- 即使你认为只是“测试环境”使用 IP 访问,也不该理所应当地关闭验证——随时可能误用到生产环境。
-
不同场景对 TLS 验证的需求
- 开发/调试阶段:快速完成接口调试,往往是使用自签名证书或无证书的测试服务;临时关闭验证可以提高效率,但请勿忽视潜在风险。
- 本地模拟/测试环境:可以搭建私有 CA,生成测试用证书,并将 CA 根证书导入到本地信任存储以实现完整验证。
- 预发布/Staging 环境:建议使用与生产相同的证书类型(例如 Let’s Encrypt 或公司自建 PKI),确保和生产环境的 TLS 配置一致。
- 生产环境:必须使用经公认 CA(例如 Let’s Encrypt、DigiCert、GlobalSign 等)签发的、域名/IP 完全匹配的证书,并配置好自动续期机制。
3. 不推荐的“临时”解决办法
⚠️ 强烈建议仅在开发/测试环境、或者你已经完全了解风险并能够接受时使用下面策略,切勿在生产环境使用。
3.1. 直接禁止警告输出
将 urllib3
的 InsecureRequestWarning
置为忽略状态。常见做法:
import urllib3urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)import requestsresponse = requests.get(\'https://47.113.219.226/api/xxx\', verify=False)print(response.status_code, response.text)
-
上述代码对全局生效:所有跳过证书验证的请求均不会再打印 InsecureRequestWarning。
-
缺点
- 虚假的安全感:抑制了警告,却仍旧未执行证书验证。
- 生产中误用风险:容易忘记恢复验证,一旦上线,就等于对所有 HTTPS 请求一律“信任”。
3.2. 在单次请求中关闭验证(verify=False
)
只针对某一次请求关闭验证,示例:
import requestsresponse = requests.get( \'https://47.113.219.226/api/data\', verify=False # 关闭证书验证,直接忽略警告(若不关闭,仍会抛出 InsecureRequestWarning))print(response.status_code)
-
这是官方推荐的、用于“临时调试”的方式。
-
如果不想让警告影响控制台输出,需要配合
urllib3.disable_warnings()
(参见 3.1)。 -
缺点
- 请求完全不验证服务器身份,易受中间人攻击。
- 代码可读性差,容易遗漏“哪儿用了 verify=False”,也难以搜寻和统一管理。
4. 推荐的“根本”解决办法:证书校验与信任链
核心思路:始终让客户端对服务器证书进行校验,并且让服务器呈现一个“可信任的、证书链完整的” TLS 证书。本文将分层介绍不同场景下的具体做法。
4.1. 使用操作系统/Python 自带的信任根存储
- 大部分现代操作系统(例如 Windows、macOS、主流 Linux 发行版)都自带了一个“根 CA 存储(Trust Store)”,其中包含了全球常见公共 CA 的根证书。
- Python(尤其是使用系统 Python 自带的
ssl
模块)在默认情况下也会去调用系统的信任存储来验证服务器证书,无需额外操作。
4.1.1. 本地已经有受信任的证书时直接使用
假设目标服务器为 https://47.113.219.226
,已经绑定了一个由公认 CA(如 Let’s Encrypt、DigiCert)签发的证书,并且 IP 地址与证书中 Subject Alternative Name (SAN)
匹配。那么直接在 Python 中写:
import requestsresponse = requests.get(\'https://47.113.219.226/api/info\')print(response.status_code, response.json())
即可完成证书校验,不需要额外传入 verify
参数;如果服务器证书链完整且根 CA 在操作系统信任库中,就不会出现警告,也不会因证书验证失败而抛出异常。
注意:
- 当前例子中以 IP 访问(
47.113.219.226
),但绝大多数 CA 不会给纯 IP 签发公开证书(Let’s Encrypt 也仅支持域名)。- 如果只是简单测试 IP 地址对 HTTPS 的访问,很可能需要使用自签名证书,或在证书中把该 IP 写入 SAN 才能正常通过校验。
4.2. 使用 certifi
库维护信任根
requests
默认会调用certifi.where()
所指向的 CA 根证书集合文件。- 这种方式下,不依赖机器自带的信任存储,而是依赖
certifi
的独立集合,确保跨平台一致性。
4.2.1. 验证 requests
所使用的 CA 根文件路径
import certifiprint(certifi.where())# 例如输出:/usr/local/lib/python3.9/site-packages/certifi/cacert.pem
- 当你安装了
requests
(通常会一同安装certifi
),默认requests.get()
会使用certifi.where()
返回的.pem
文件来做验证。
4.2.2. 强制指定使用 certifi
的 CA 根
如果想显式地让某个请求使用 certifi
而不是系统的信任库,可以这样写:
import requestsimport certifiresponse = requests.get( \'https://47.113.219.226/api/data\', verify=certifi.where())print(response.status_code)
-
如果你在一个没有系统 CA 存储的环境(或该环境里的
requests
未启用系统存储),可以考虑此方案。 -
缺点:
- 如果想使用私有 CA 或自签名 CA,需要把相应 CA 的根证书追加到
certifi
的cacert.pem
中,操作相对麻烦。
- 如果想使用私有 CA 或自签名 CA,需要把相应 CA 的根证书追加到
4.3. 自定义并指定 CA 证书文件
当服务器使用了自签名证书,或者是公司内部 PKI 签发的私有 CA 时,客户端需要拿到该 CA 根证书,然后在请求时显式指定。常见步骤如下。
4.3.1. 从服务器/运维团队获取根证书
-
自签名:如果服务器使用的是自签名证书(
.crt
或.pem
格式),请确认服务端将该证书完整地暴露给客户端或通过私有存储分发。 -
私有 CA:如果是公司内部 PKI 签发,则需要同时提供“根证书 CA.pem”+可能的“中间证书 intermediate.pem”。
-
合并成单一 PEM 文件:
-
如果有根 CA 和中间 CA,可以使用以下命令把它们合并成一个
company_ca_bundle.pem
:cat intermediate.pem root_ca.pem > company_ca_bundle.pem
-
确保顺序是“中间 CA 在前、根 CA 在后”。
-
4.3.2. 在请求时指定 verify=CA_BUNDLE_PATH
Python 代码示例:
import requests# 假设 company_ca_bundle.pem 已经放在当前目录CA_BUNDLE_PATH = \'./company_ca_bundle.pem\'response = requests.get( \'https://47.113.219.226/api/secure\', verify=CA_BUNDLE_PATH)print(response.status_code, response.text)
- 此时,
requests
会使用指定的company_ca_bundle.pem
验证服务器端证书。 - 如果服务器端证书链正确、证书未过期、IP(或域名)在证书的 SAN 中匹配,则请求成功;否则,会抛出
requests.exceptions.SSLError
。
4.3.3. 验证过程中的常见错误
-
certificate verify failed
/SSLCertVerificationError
- 一般是 CA bundle 中缺少了真正签发者的根证书;或顺序颠倒。
- 用
openssl s_client -connect 47.113.219.226:443 -showcerts
来查看服务端提供的证书链,核对 CA 链是否与本地company_ca_bundle.pem
完全一致。
-
IP/域名不匹配
-
如果访问的是 IP 而证书里只有域名(Common Name 或 SAN)而没有 IP,则会提示“hostname ‘47.113.219.226’ doesn’t match either of ‘example.com’”。
-
解决:
- 在生成证书时,将 IP 地址写进 SAN;
- 或者用域名访问并确保域名指向该 IP;
- 或者使用
urllib3.disable_warnings()
+verify=False
(仅测试环境)。
-
4.4. 针对自签名/私有 CA 的证书配置
在企业内部或者本地测试环境,往往会自己搭建私有 CA 并对各服务签发自签名证书。以下是一整套流程示例,涵盖“如何从零开始生成自签名 CA → 签发服务器证书 → 在客户端配置验证”。
4.4.1. 使用 OpenSSL 创建根 CA(仅示例,生产环境需更高安全性)
-
生成根 CA 的私钥与自签名根证书
# 生成根 CA 私钥(2048 位 RSA)并加密(可选密码,这里示例无密码)openssl genrsa -out rootCA.key 2048# 生成根 CA 证书(自签名),有效期10年openssl req -x509 -new -nodes -key rootCA.key \\ -sha256 -days 3650 \\ -subj \"/C=CN/ST=Beijing/L=Beijing/O=MyCompany Ltd./OU=IT/CN=MyCompany Root CA\" \\ -out rootCA.pem
-
生成服务器端私钥及证书签发请求(CSR)
# 生成服务器私钥openssl genrsa -out server.key 2048# 生成 CSR(此处以 IP 举例;如果只用域名,CN 写域名即可,同时要给 SAN 配置 IP)openssl req -new -key server.key \\ -subj \"/C=CN/ST=Beijing/L=Beijing/O=MyCompany Ltd./OU=IT/CN=47.113.219.226\" \\ -out server.csr# 生成一个 v3 配置文件,使证书中包含 Subject Alternative Name(SAN)cat > server_ext.cnf <<EOFauthorityKeyIdentifier=keyid,issuerbasicConstraints=CA:FALSEkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEnciphermentsubjectAltName = @alt_names[alt_names]IP.1 = 47.113.219.226DNS.1 = 47.113.219.226# 如果你想同时支持域名访问,可在下面加上# DNS.2 = example.mycompany.comEOF
-
使用根 CA 签发服务器证书
openssl x509 -req -in server.csr \\ -CA rootCA.pem -CAkey rootCA.key -CAcreateserial \\ -out server.crt -days 825 -sha256 \\ -extfile server_ext.cnf
rootCA.srl
:记录了根 CA 的序列号,用于后续签发。server.crt
:由根 CA 签名的服务器证书,可用于部署到 Nginx/Apache 等。
-
将
rootCA.pem
(根 CA 证书)分发给所有需要信任该证书的客户端- 在 Python 客户端,将
rootCA.pem
(或与intermediate.pem
合并后的bundle.pem
)放到某个固定路径。 - 代码示例与 4.3.2 中介绍的“
verify
指定 CA_BUNDLE”相同。
- 在 Python 客户端,将
4.4.1.1. 在服务器(如 Nginx)中部署自签名证书示例
server { listen 443 ssl; server_name 47.113.219.226; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; # 如果有中间证书,则使用 cat 合并:cat intermediate.crt >> server.crt # ssl_certificate /etc/nginx/ssl/server_fullchain.crt; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:8080; # ... 其他代理配置 }}
-
注意事项
- 如果只部署了
server.crt
,但并没有把中间证书/根证书链一起拼接在一起,就会出现“证书链不完整”导致校验失败。 - 最好将
server.crt + intermediate.pem + rootCA.pem
合并为fullchain.pem
,在ssl_certificate
中一起提供。
- 如果只部署了
4.5. 将证书打包到可执行文件或 Docker 镜像中
-
如果你的客户端是一个打包成可执行文件的工具(例如使用 PyInstaller 打包后的 exe),需要把 CA 根证书或 bundle 一同打包进去。
-
Docker 镜像:
- 在构建镜像时,将自定义 CA 根证书复制到镜像内系统信任存储(Windows 下添加到证书存储,Linux 下通常复制到
/usr/local/share/ca-certificates/
并执行update-ca-certificates
)。
- 在构建镜像时,将自定义 CA 根证书复制到镜像内系统信任存储(Windows 下添加到证书存储,Linux 下通常复制到
4.5.1. 示例:在 Dockerfile 中添加自签名根证书
FROM python:3.9-slim# 将自签名根证书复制到镜像COPY rootCA.pem /usr/local/share/ca-certificates/rootCA.crt# 更新系统信任证书存储RUN apt-get update && \\ apt-get install -y ca-certificates && \\ update-ca-certificates# 安装 Python 依赖COPY requirements.txt /app/RUN pip install --no-cache-dir -r /app/requirements.txt# 复制应用代码COPY . /app/WORKDIR /app/CMD [\"python\", \"app.py\"]
此时,容器内的 Python 程序发起的所有 HTTPS 请求都会默认信任 rootCA.pem
。
5. 多种编程语言/框架下的同类解决思路
虽然本博客重点关注 Python requests
抛出的 InsecureRequestWarning
,但其他语言或工具也会提示类似警告,原理相通:缺少可信 CA 或验证逻辑被关闭。下面列出常见环境下的对策,仅供参考。
5.1. curl
命令行下的 TLS 验证
-
禁止验证:
curl -k https://47.113.219.226/api/test# 或者等同于 --insecure
此时
curl
不会验证服务器证书,风险同样存在。 -
指定 CA 证书:
curl --cacert /path/to/company_ca_bundle.pem https://47.113.219.226/api/test
如果有中间证书,则同样要把它们“cat”到一起形成一个 bundle。
-
指定证书目录(将证书放到目录内):
curl --capath /etc/ssl/company_ca/
capath
要求该目录下的 CA 证书都必须是.crt
文件,并且以哈希命名。 -
验证常见错误:
curl: (60) SSL certificate problem: unable to get local issuer certificate
通常是缺少中间链/根证书。curl: (51) SSL: certificate subject name ‘47.113.219.226’ does not match target host name ‘example.com’
则是 IP/域名不匹配。
5.2. Node.js(axios
、request
)下的类似警告
-
axios
默认会继承 Node.js 的 TLS 验证策略。-
关闭验证(不推荐,仅测试环境):
const axios = require(\'axios\');process.env.NODE_TLS_REJECT_UNAUTHORIZED = \'0\';// 或者针对单个请求:axios.get(\'https://47.113.219.226/api/data\', { httpsAgent: new require(\'https\').Agent({ rejectUnauthorized: false }) }) .then(resp => console.log(resp.data)) .catch(err => console.error(err));
警告:一旦
NODE_TLS_REJECT_UNAUTHORIZED=0
,将全局关闭所有 HTTPS 请求的验证,极度不安全。 -
使用自定义 CA:
const fs = require(\'fs\');const https = require(\'https\');const axios = require(\'axios\');const ca = fs.readFileSync(\'./company_ca_bundle.pem\');const agent = new https.Agent({ ca: ca, // 用自签名/私有 CA 进行验证 keepAlive: true,});axios.get(\'https://47.113.219.226/api/data\', { httpsAgent: agent }) .then(resp => console.log(resp.data)) .catch(err => console.error(err));
-
-
request
(已废弃,但仍有项目在用)- 关闭验证:
request({ url: \'https://47.113.219.226\', strictSSL: false }, callback);
- 指定 CA:
request({ url: \'https://47.113.219.226\', ca: fs.readFileSync(\'./company_ca_bundle.pem\') }, callback);
- 关闭验证:
5.3. Java(HttpClient
、OkHttp
)下的 TLS 验证配置
-
Apache HttpClient(4.x)
-
关闭验证(不推荐,仅测试):
SSLContext sslContext = SSLContext.getInstance(\"SSL\");sslContext.init(null, new TrustManager[] { new X509TrustManager() { public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }}}, new SecureRandom());SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, NoopHostnameVerifier.INSTANCE);CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(sslSocketFactory) .build();HttpGet get = new HttpGet(\"https://47.113.219.226/resource\");CloseableHttpResponse resp = httpClient.execute(get);
上述代码将完全信任任何证书,极其危险。
-
使用自定义
TrustStore
:-
将根 CA 导入到一个 JKS 或 PKCS12 格式的信任库:
keytool -importcert -alias mycompanyca -file rootCA.pem -keystore truststore.jks -storepass changeit
-
在代码中加载该
truststore.jks
:KeyStore trustStore = KeyStore.getInstance(\"JKS\");try (FileInputStream instream = new FileInputStream(new File(\"truststore.jks\"))) { trustStore.load(instream, \"changeit\".toCharArray());}SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(trustStore, null) .build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext, new DefaultHostnameVerifier());CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build();
-
使用
httpClient
发起请求即可完成 SSL 验证。
-
-
-
OkHttp(Square 出品)
-
关闭验证(仅测试):
OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(getUnsafeSSLContext().getSocketFactory(), getUnsafeTrustManager()) .hostnameVerifier((hostname, session) -> true) .build();
(
getUnsafeSSLContext()
与getUnsafeTrustManager()
返回一个信任所有证书的SSLContext
与TrustManager
,同样危险。) -
使用自定义 CA:
CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");InputStream caInput = new FileInputStream(\"company_ca.pem\");Certificate ca = cf.generateCertificate(caInput);caInput.close();KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null, null);keyStore.setCertificateEntry(\"ca\", ca);TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());tmf.init(keyStore);SSLContext sslContext = SSLContext.getInstance(\"TLS\");sslContext.init(null, tmf.getTrustManagers(), null);OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager)tmf.getTrustManagers()[0]) .build();
-
5.4. Go 语言下的 http.Client
TLS 配置
-
Go 默认会使用系统根证书来验证,如果服务器证书有效,一般不需要额外配置。
-
关闭验证(仅测试):
tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}client := &http.Client{Transport: tr}resp, err := client.Get(\"https://47.113.219.226/api\")
-
加载自定义根证书:
caCert, err := ioutil.ReadFile(\"company_ca.pem\")if err != nil { log.Fatalf(\"Unable to read CA cert: %v\", err)}caCertPool := x509.NewCertPool()caCertPool.AppendCertsFromPEM(caCert)tlsConfig := &tls.Config{ RootCAs: caCertPool,}transport := &http.Transport{TLSClientConfig: tlsConfig}client := &http.Client{Transport: transport}resp, err := client.Get(\"https://47.113.219.226/api\")
5.5. .NET(HttpClient
)下的 Server Certificate 验证
-
关闭验证(仅测试):
var handler = new HttpClientHandler();handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;var client = new HttpClient(handler);var response = await client.GetAsync(\"https://47.113.219.226/api\");
-
使用自定义根证书:
-
将根证书以 PEM/DER 格式加载,转换为
X509Certificate2
:X509Certificate2 caCert = new X509Certificate2(\"company_ca.cer\");var handler = new HttpClientHandler();handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>{ // 在链的根位置插入自定义 CA chain.ChainPolicy.ExtraStore.Add(caCert); chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; bool isValid = chain.Build(cert); // 也可以检查 cert.Subject 是否匹配或检查 cert.Issuer 匹配 return isValid;};var client = new HttpClient(handler);var response = await client.GetAsync(\"https://47.113.219.226/api\");
-
6. 对比与实践:如何从开发到生产确保 HTTPS 安全
在不同环境切换时,往往会使用不同程度的“放宽”或严格的 TLS 验证策略。以下结合实践,给出一个由浅入深的最佳实践流程。
6.1. 在本地/测试环境中调试时的最佳实践
-
原则:尽量保持与生产环境一致的验证流程。
-
推荐做法:搭建一个由“私有 CA”签发的测试证书
- 生成一个私有根 CA 并导入到本地系统信任存储(或者在
certifi
中追加、或通过 Python 指定verify
)。 - 对测试服务器签发证书,确保在本地访问时不会出现“证书验证失败”。
- 这样既能“用 HTTPS”,又能避免关闭验证所带来的安全隐患。
- 生成一个私有根 CA 并导入到本地系统信任存储(或者在
-
如果确实需要临时关闭验证
-
一定要在代码中显著标注:例如在 request 前后写好注释
# TODO: 仅测试环境关闭验证,发布请移除
。 -
不要直接修改全局抑制;尽量用内联
verify=False
限定作用域:# NOTE: 本行仅在 dev 环境下关闭验证,prod 环境需要改为 verify=\'/path/to/ca.pem\'response = requests.get(url, verify=False)
-
-
始终保持私有 CA 的私钥/根证书文件在版本管理之外,不要泄露。
6.2. 在 Staging/预发布环境中引入 CA
-
原则:Staging 环境尽量使用可公开访问的证书,如 Let’s Encrypt、或者已导入业务私有 CA(与生产环境相同或同级别 PKI)。
-
如果使用 Let’s Encrypt:
- 绑定一个二级域名(例如
staging.example.com
),让 DNS 指向测试服务器 IP。 - 使用
certbot
申请 Let’s Encrypt 证书,自动续期。
- 绑定一个二级域名(例如
-
如果是私有 CA:
- 将私有根 CA 导入到 Staging 环境操作系统信任存储。
- 在运行时,客户端同样使用系统信任库(比如
requests
默认即可)来校验证书。
-
这么做的好处:Staging 与生产环境做“真实模拟”,一旦证书链或续期脚本出现问题,能及早发现。
6.3. 生产环境中的 TLS 证书申请与自动续期流程
-
Let’s Encrypt/ACME 自动化
-
使用
certbot
(或其它 ACME 客户端)自动申请和续期。 -
在 Nginx/Apache/Traefik/HAProxy 等反向代理中配置 自动热加载。
-
流程示例:
certbot certonly --webroot -w /var/www/html -d example.com
- 在 Cron 中执行
certbot renew --post-hook \"systemctl reload nginx\"
-
让 HTTPS 证书自动化,降低人为失误。
-
-
企业/私有 CA 集成
- 搭建一个内部 PKI:使用 EJBCA、OpenCA、HashiCorp Vault PKI、微软 ADCS 等。
- 定期签发并下发“中间 CA 证书 + 工作证书(用于各服务)”,并将“中间 CA”加入操作系统信任存储或通过集中化配置管理分发。
- 生产环境强制客户端(包括人、应用)均使用操作系统/语言环境自带的信任存储,减少手动指定
verify=
的麻烦。
-
统一监控与预警
-
建议对证书到期时间建立监控告警:
- 可以在 Prometheus + Blackbox Exporter 中检测
https://example.com
的证书有效期; - 或者使用外部服务(如 Let’s Monitor、SSL Labs API)定期获取证书过期日期,提前提醒运维同学。
- 可以在 Prometheus + Blackbox Exporter 中检测
-
-
合理配置客户端超时时间与重试策略
- 为网络请求设置合理的超时时间(
timeout=5
秒之类),避免请求长期挂起。 - 如果客户端使用了负载均衡或多节点集群,对链路不稳定或证书更新时短暂失效的情况,要有重试或快速退避(Exponential Backoff)机制,避免大量 5xx 错误影响业务。
- 为网络请求设置合理的超时时间(
7. 常见故障排查方法与排错技巧
在排查 InsecureRequestWarning
及其它 TLS/SSL 相关错误时,可以参考以下思路和工具。
7.1. 使用 openssl s_client
或 openssl x509
验证服务器证书
-
检查服务器收益到的证书链
openssl s_client -connect 47.113.219.226:443 -showcerts
-
If the server sends only its leaf certificate but没有中间 CA,则会看到类似:
depth=0 CN = 47.113.219.226verify error:num=20:unable to get local issuer certificate
-
你需要等到输出完整链信息后,确保包含“中间证书”与“根证书”。
-
-
提取并转换服务器的 Leaf Certificate
openssl s_client -connect 47.113.219.226:443 -showcerts </dev/null 2>/dev/null \\ | openssl x509 -outform PEM > server_leaf.pem
-
然后可以进一步:
openssl x509 -in server_leaf.pem -text -noout
查看证书有效期、颁发者(Issuer)、签发者(Subject)、SAN 列表等信息。
-
特别关注:
Not Before
/Not After
:证书是否过期Issuer
是否为你所信任的 CASubject Alternative Name
是否包含 IP 或域名
-
-
验证本地手头的 CA 文件是否能验证服务器证书
openssl verify -CAfile company_ca_bundle.pem server_leaf.pem
- 如果输出
server_leaf.pem: OK
,说明用这个 CA bundle 能通过对 Leaf 证书的验证。 - 否则输出错误提示(如
unable to get local issuer certificate
),说明你本地缺少上游某个中间证书。
- 如果输出
7.2. 检查中间证书链是否完整
-
大多数生产环境错在“只部署了 Leaf+Intermediate1,但缺少了 Intermediate2 或 根 CA”。
-
最终验证流程是:“Leaf → Intermediate → … → Root CA → Trust Store”,任何一环断裂都会验证失败。
-
建议:
- 让服务器运维人员提供完整链(通常称为
fullchain.pem
)。 - 用
openssl s_client
确认链路。
- 让服务器运维人员提供完整链(通常称为
7.3. 跨平台时区/系统证书更新导致的问题
- 某些 Linux 发行版旧版本的
ca-certificates
包已过期,可能缺少新的根 CA;即使在本地机器能通过,切换到 CI 环境就可能“找不到根 CA”。 - 如果用
certifi
,可以通过pip install --upgrade certifi
来同步到最新的根 CA 列表。 - Windows 上某些自签名 CA 导入方式:需要在“受信任的根证书颁发机构” -> “证书” 中手动导入
.pem
→ 生效。
7.4. 证书过期、域名/IP 不匹配等常见错误
-
证书过期
- 客户端会提示
certificate has expired
。 - 需要将服务器上的旧证书替换成新证书(Let’s Encrypt 自动续期,或手动签发更换)。
- 客户端会提示
-
主机名(IP)与证书不匹配
-
常见报错:
hostname \"47.113.219.226\" does not match \"example.com\"
。 -
解决:
- 在证书签发时将 IP 写进 SAN(仅自签名/私有 CA 支持)。
- 或者改为用域名访问,并确保 DNS 解析到正确的 IP。
- 如果你执意用 IP 访问,所有客户端均需要在
verify
时额外提供一个“忽略 host 验证”的回调(例如 Node.js 里可写.hostnameVerifier(() => true)
,但强烈不推荐在生产这样做)。
-
-
中间证书缺失
- 如前文所述,部署时要把中间 CA 拼接到 server 端配置中。
- 如果 “
openssl s_client
” 输出显示链不完整,则立刻修复。
8. 总结与建议
- 绝不在生产环境中使用
verify=False
或者全局禁用InsecureRequestWarning
。 - 强烈推荐从开发早期就使用“私有 CA + 本地信任” 的模式,做到开发/测试 与 生产“TLS 验证”流程的无缝衔接。
- Let’s Encrypt 等公共 CA 是最常见的免费签发方式,但它们对“纯 IP 证书”不支持;使用它们时请务必绑定域名。
- 涉及到 IP HTTPS 访问的场景,最常用的方式是“自签名 CA + SAN 中加入 IP”,然后让客户端一并信任该自签名 CA。
- 务必检查证书链的完整性:Leaf → 一或多级中间 CA → Root CA → Trust Store。
- 在代码层面,务必做到“即便是测试环境也要显式标注/文档化关闭验证的地方”,提高可维护性,防止误部署。
- 自动化证书续期与监控预警不可或缺:让你的业务不至于因为证书到期而“全网告警”,也能及时发现链路中断导致的证书校验故障。
9. 附录:参考链接与资料汇总
-
Python
requests
文档:Advanced Usage / SSL Warnings- https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
-
certifi
官方仓库与说明- https://github.com/certifi/python-certifi
-
OpenSSL 常用命令手册
man openssl
/ https://www.openssl.org/docs/man1.1.1/
-
Let’s Encrypt 官方文档
- https://letsencrypt.org/docs/
-
Node.js TLS/SSL 支持文档
- https://nodejs.org/api/tls.html
-
Apache HttpClient SSL/TLS 配置
- https://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e217
-
OkHttp TLS 教程
- https://square.github.io/okhttp/ssl/
-
Go 标准库
crypto/tls
文档- https://pkg.go.dev/crypto/tls
-
.NET
HttpClient
HTTPS 配置示例- https://docs.microsoft.com/dotnet/api/system.net.http.httpclienthandler.servercertificatecustomvalidationcallback
本文涵盖了从“最简单的抑制警告”到“企业级 TLS 证书管理”的整套思路与实践细节。希望对你研发、测试、运维各环节都能有实质帮助,让你的 HTTPS 通信既符合安全规范,又在开发效率与维护成本之间取得平衡。祝顺利解决 InsecureRequestWarning: Unverified HTTPS request
!
粉丝福利
👉 更多信息:有任何疑问或者需要进一步探讨的内容,欢迎点击文末名片获取更多信息。我是猫头虎博主,期待与您的交流! 🦉💬
联系我与版权声明 📩
- 联系方式:
- 微信: Libin9iOak
- 公众号: 猫头虎技术团队
- 版权声明:
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问猫头虎的博客首页。
点击✨⬇️下方名片
⬇️✨,加入猫头虎AI共创社群矩阵。一起探索科技的未来,共同成长。🚀
🔗 猫头虎抱团AI共创社群 | 🔗 Go语言VIP专栏 | 🔗 GitHub 代码仓库 | 🔗 Go生态洞察专栏 ✨ 猫头虎精品博文