Flutter/Dart使用自签名HTTPS证书出现CERTIFICATE_VERIFY_FAILED错误如何解决
问题核心原因
Dart 2.17及以上版本对HTTPS证书的校验逻辑升级,不再兼容仅通过Common Name(CN)字段匹配域名的证书,强制要求证书携带Subject Alternative Name(SAN)扩展字段。Postman、curl等工具目前仍兼容旧的CN校验逻辑,所以会出现第三方工具请求正常、仅Dart/Flutter端报错的情况,这是90%以上同类问题的根因。
排查&修复步骤
1. 修复自签名证书生成逻辑(最优先)
你之前生成的证书缺少SAN字段,需要新增配置文件后重新生成证书:
首先创建san.cnf配置文件,填写你需要匹配的域名/IP:
[req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn req_extensions = req_ext [dn] C = CN ST = 你的省份 L = 你的城市 O = 你的公司名 OU = 你的部门名 CN = localhost [req_ext] subjectAltName = @alt_names extendedKeyUsage = serverAuth [alt_names] DNS.1 = localhost IP.1 = 127.0.0.1 # 如有其他域名/访问IP,按序号往下加即可,比如 DNS.2 = test.com
然后按以下命令重新生成整套证书:
- 生成根私钥:
openssl genrsa -out rootCA.key 2048 - 生成根CA证书:
openssl req -x509 -new -key rootCA.key -sha256 -days 365 -out rootCA.pem -config san.cnf -extensions req_ext - 生成服务端私钥:
openssl genrsa -out server.key 2048 - 生成服务端证书签名请求:
openssl req -new -key server.key -out server.csr -config san.cnf - 签发服务端证书:
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -days 365 -sha256 -out server.pem -extfile san.cnf -extensions req_ext - 拼接证书链:
cat server.pem rootCA.pem > chain.pem
生成完成后执行以下命令验证证书是否携带SAN字段:openssl x509 -in server.pem -text | grep -A 10 "Subject Alternative Name"
正常会输出你配置的域名/IP信息,没有输出则说明证书生成错误。
2. 调整客户端代码
初始化SecurityContext时明确关闭系统默认根证书,避免冲突:
void main() async { print('Making request.'); // 关闭系统默认根证书,仅信任你传入的自定义证书 final SecurityContext clientSecurityContext = SecurityContext(withTrustedRoots: false); // 确认rootCA.pem加载正确,可打印字节长度和本地文件大小对比 final caBytes = await File('rootCA.pem').readAsBytes(); print('加载CA证书长度:${caBytes.length}'); clientSecurityContext.setTrustedCertificatesBytes(caBytes); final HttpClient httpClient = HttpClient(context: clientSecurityContext); // 临时开启badCertificateCallback打印校验失败原因,测试完成后改回return false httpClient.badCertificateCallback = (X509Certificate cert, String host, int port){ print("证书校验失败:host=$host:$port,证书主体=${cert.subject},签发者=${cert.issuer}"); // 测试阶段可临时返回true,确认能正常请求后再关闭 return false; }; final HttpClientRequest request = await httpClient.getUrl(Uri(scheme: 'https', host: 'localhost', port: 443, path: '/')); final HttpClientResponse response = await request.close(); // 后续逻辑不变 }
3. 移动端额外配置(仅Flutter移动端需要)
如果是在安卓7及以上版本运行,系统默认不信任用户自定义CA证书,需要额外配置网络安全策略:
- 在
android/app/src/main/res下新建xml目录,创建network_security_config.xml文件:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">localhost</domain> <trust-anchors> <!-- 把rootCA.pem放到android/app/src/main/res/raw目录下 --> <certificates src="@raw/rootCA"/> <certificates src="system"/> </trust-anchors> </domain-config> </network-security-config>
- 在
android/app/src/main/AndroidManifest.xml的application标签添加属性:android:networkSecurityConfig="@xml/network_security_config"
4. 其他排查点
- 确认证书文件没有多余的不可见字符、BOM头,纯文本PEM格式仅包含
-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----包裹的内容 - 确认设备时间在证书有效期范围内,不会因为时间超前/滞后导致证书过期校验失败
- 如果用的是第三方HTTP库比如dio,确认库的配置没有覆盖全局的SecurityContext
内容的提问来源于stack exchange,提问作者Parafin
相关产品推荐
相关产品推荐

