
如何解决 InsecureRequestWarning: Unverified HTTPS request is being made to host '47.113.219.226'. Adding certificate verification is strongly advised. 问题(全网解决方案大全)
在使用 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名称统一为猫头虎或猫头虎技术团队。
我希望通过我的分享,帮助大家更好地掌握和使用各种技术产品,提升开发效率与体验。
作者名片 ✍️
部分专栏链接:
🔗 精选专栏:
InsecureRequestWarning:
requests 库(或底层的 urllib3)默认会尝试用 certifi 提供的 CA 根证书来验证对方证书。requests 会因为“证书校验失败”而报错/抛出异常。requests 支持一个参数 verify=False,用于跳过证书验证。但同时也会触发 InsecureRequestWarning 警告,提醒你“这很不安全”。⚠️ 强烈建议仅在开发/测试环境、或者你已经完全了解风险并能够接受时使用下面策略,切勿在生产环境使用。
将 urllib3 的 InsecureRequestWarning 置为忽略状态。常见做法:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import requests
response = requests.get('https://47.113.219.226/api/xxx', verify=False)
print(response.status_code, response.text)verify=False)只针对某一次请求关闭验证,示例:
import requests
response = requests.get(
'https://47.113.219.226/api/data',
verify=False # 关闭证书验证,直接忽略警告(若不关闭,仍会抛出 InsecureRequestWarning)
)
print(response.status_code)urllib3.disable_warnings()(参见 3.1)。
核心思路:始终让客户端对服务器证书进行校验,并且让服务器呈现一个“可信任的、证书链完整的” TLS 证书。本文将分层介绍不同场景下的具体做法。
ssl 模块)在默认情况下也会去调用系统的信任存储来验证服务器证书,无需额外操作。假设目标服务器为 https://47.113.219.226,已经绑定了一个由公认 CA(如 Let’s Encrypt、DigiCert)签发的证书,并且 IP 地址与证书中 Subject Alternative Name (SAN) 匹配。那么直接在 Python 中写:
import requests
response = requests.get('https://47.113.219.226/api/info')
print(response.status_code, response.json())即可完成证书校验,不需要额外传入 verify 参数;如果服务器证书链完整且根 CA 在操作系统信任库中,就不会出现警告,也不会因证书验证失败而抛出异常。
注意:
47.113.219.226),但绝大多数 CA 不会给纯 IP 签发公开证书(Let’s Encrypt 也仅支持域名)。certifi 库维护信任根requests 默认会调用 certifi.where() 所指向的 CA 根证书集合文件。certifi 的独立集合,确保跨平台一致性。requests 所使用的 CA 根文件路径import certifi
print(certifi.where())
# 例如输出:/usr/local/lib/python3.9/site-packages/certifi/cacert.pemrequests(通常会一同安装 certifi),默认 requests.get() 会使用 certifi.where() 返回的 .pem 文件来做验证。certifi 的 CA 根如果想显式地让某个请求使用 certifi 而不是系统的信任库,可以这样写:
import requests
import certifi
response = requests.get(
'https://47.113.219.226/api/data',
verify=certifi.where()
)
print(response.status_code)requests 未启用系统存储),可以考虑此方案。
certifi 的 cacert.pem 中,操作相对麻烦。当服务器使用了自签名证书,或者是公司内部 PKI 签发的私有 CA 时,客户端需要拿到该 CA 根证书,然后在请求时显式指定。常见步骤如下。
自签名:如果服务器使用的是自签名证书(.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 在后”。
verify=CA_BUNDLE_PATHPython 代码示例:
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 验证服务器端证书。requests.exceptions.SSLError。certificate verify failed / SSLCertVerificationError
openssl s_client -connect 47.113.219.226:443 -showcerts 来查看服务端提供的证书链,核对 CA 链是否与本地 company_ca_bundle.pem 完全一致。urllib3.disable_warnings()+verify=False(仅测试环境)。在企业内部或者本地测试环境,往往会自己搭建私有 CA 并对各服务签发自签名证书。以下是一整套流程示例,涵盖“如何从零开始生成自签名 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 <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
IP.1 = 47.113.219.226
DNS.1 = 47.113.219.226
# 如果你想同时支持域名访问,可在下面加上
# DNS.2 = example.mycompany.com
EOF使用根 CA 签发服务器证书
openssl x509 -req -in server.csr \
-CA rootCA.pem -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 825 -sha256 \
-extfile server_ext.cnfrootCA.srl:记录了根 CA 的序列号,用于后续签发。server.crt:由根 CA 签名的服务器证书,可用于部署到 Nginx/Apache 等。将 rootCA.pem(根 CA 证书)分发给所有需要信任该证书的客户端
rootCA.pem(或与 intermediate.pem 合并后的 bundle.pem)放到某个固定路径。verify 指定 CA_BUNDLE”相同。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 中一起提供。/usr/local/share/ca-certificates/ 并执行 update-ca-certificates)。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。
虽然本博客重点关注 Python requests 抛出的 InsecureRequestWarning,但其他语言或工具也会提示类似警告,原理相通:缺少可信 CA 或验证逻辑被关闭。下面列出常见环境下的对策,仅供参考。
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/域名不匹配。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);request({ url: 'https://47.113.219.226', ca: fs.readFileSync('./company_ca_bundle.pem') }, callback);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();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")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");在不同环境切换时,往往会使用不同程度的“放宽”或严格的 TLS 验证策略。以下结合实践,给出一个由浅入深的最佳实践流程。
原则:尽量保持与生产环境一致的验证流程。
推荐做法:搭建一个由“私有 CA”签发的测试证书
certifi 中追加、或通过 Python 指定 verify)。如果确实需要临时关闭验证
一定要在代码中显著标注:例如在 request 前后写好注释 # TODO: 仅测试环境关闭验证,发布请移除。
不要直接修改全局抑制;尽量用内联 verify=False 限定作用域:
# NOTE: 本行仅在 dev 环境下关闭验证,prod 环境需要改为 verify='/path/to/ca.pem'
response = requests.get(url, verify=False)始终保持私有 CA 的私钥/根证书文件在版本管理之外,不要泄露。
staging.example.com),让 DNS 指向测试服务器 IP。certbot 申请 Let’s Encrypt 证书,自动续期。requests 默认即可)来校验证书。certbot(或其它 ACME 客户端)自动申请和续期。
certbot certonly --webroot -w /var/www/html -d example.comcertbot renew --post-hook "systemctl reload nginx"verify= 的麻烦。https://example.com 的证书有效期;timeout=5 秒之类),避免请求长期挂起。在排查 InsecureRequestWarning 及其它 TLS/SSL 相关错误时,可以参考以下思路和工具。
openssl s_client 或 openssl x509 验证服务器证书检查服务器收益到的证书链
openssl s_client -connect 47.113.219.226:443 -showcertsIf the server sends only its leaf certificate but没有中间 CA,则会看到类似:
depth=0 CN = 47.113.219.226
verify 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.pemserver_leaf.pem: OK,说明用这个 CA bundle 能通过对 Leaf 证书的验证。unable to get local issuer certificate),说明你本地缺少上游某个中间证书。fullchain.pem)。openssl s_client 确认链路。ca-certificates 包已过期,可能缺少新的根 CA;即使在本地机器能通过,切换到 CI 环境就可能“找不到根 CA”。certifi,可以通过 pip install --upgrade certifi 来同步到最新的根 CA 列表。.pem → 生效。certificate has expired。hostname "47.113.219.226" does not match "example.com"。
verify 时额外提供一个“忽略 host 验证”的回调(例如 Node.js 里可写 .hostnameVerifier(() => true),但强烈不推荐在生产这样做)。openssl s_client” 输出显示链不完整,则立刻修复。verify=False 或者全局禁用 InsecureRequestWarning。requests 文档:Advanced Usage / SSL Warnings
certifi 官方仓库与说明
man openssl / https://www.openssl.org/docs/man1.1.1/crypto/tls 文档
HttpClient HTTPS 配置示例
本文涵盖了从“最简单的抑制警告”到“企业级 TLS 证书管理”的整套思路与实践细节。希望对你研发、测试、运维各环节都能有实质帮助,让你的 HTTPS 通信既符合安全规范,又在开发效率与维护成本之间取得平衡。祝顺利解决 InsecureRequestWarning: Unverified HTTPS request!
版权声明: 本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问猫头虎的博客首页。