You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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证书,需要额外配置网络安全策略:

  1. 在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>
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 02:09:03