Flutter加载GCS的NetworkImage报CERTIFICATE_VERIFY_FAILED错误
问题描述
在Flutter中尝试加载NetworkImage资源时遇到证书校验失败报错,导致图片加载失败,初始报错信息如下:
Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:393))
- 加载失败场景:图片存储于Google Cloud Storage,搭配Cloud CDN加速,调用代码如下:
NetworkImage("https://cdn.vaandra.com/items/f69f662f-41ff-4bb1-bd50-75a70b7ca6a5/439806b1-cacc-4d58-85a5-601bcb825fe3-p-500")
- 正常加载场景:公网测试图片可正常加载,调用代码如下:
NetworkImage("https://picsum.photos/250?image=9")
完整错误栈信息:
======== Exception caught by image resource service ================================================ The following HandshakeException was thrown resolving an image codec: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:393)) When the exception was thrown, this was the stack: #0 _SecureFilterImpl._handshake (dart:io-patch/secure_socket_patch.dart:99:46) #1 _SecureFilterImpl.handshake (dart:io-patch/secure_socket_patch.dart:142:25) #2 _RawSecureSocket._secureHandshake (dart:io/secure_socket.dart:911:54) #3 _RawSecureSocket._tryFilter (dart:io/secure_socket.dart:1040:19) <asynchronous suspension> Image provider: NetworkImage("https://cdn.vaandra.com/items/f69f662f-41ff-4bb1-bd50-75a70b7ca6a5/439806b1-cacc-4d58-85a5-601bcb825fe3-p-500", scale: 1.0) Image key: NetworkImage("https://cdn.vaandra.com/items/f69f662f-41ff-4bb1-bd50-75a70b7ca6a5/439806b1-cacc-4d58-85a5-601bcb825fe3-p-500", scale: 1.0) ====================================================================================================
核心疑问:同样使用HTTPS协议,为什么公网测试图片可以正常加载,自有CDN的图片会抛出证书错误?
原因说明
HTTPS握手是否校验通过,和是否使用HTTPS协议没有直接关联,核心判断逻辑是服务端返回的证书链,能否被客户端内置的信任根证书列表逐层验证通过:
- picsum.photos这类公共站点使用全球通用公共CA签发的证书,服务端配置了完整的证书链,对应根证书默认预置在Dart SDK的信任根证书库中,握手阶段校验可以直接通过,因此图片可以正常加载。
- 自有CDN域名抛出
unable to get local issuer certificate错误,直接原因是TLS握手阶段客户端无法找到证书对应的上级签发者证书,常见触发场景有两种:- CDN侧配置证书时仅上传了站点的叶子证书,没有拼接配套的中间CA证书,导致服务端返回的证书链不完整。普通浏览器访问这类站点时,会自动通过证书内的AIA扩展地址拉取缺失的中间证书补全链,因此浏览器访问不会报证书错误,但Dart内置的
HttpClient(NetworkImage底层依赖的网络组件)没有实现自动拉取缺失中间证书的逻辑,只要返回的证书链无法直接匹配到内置信任根证书,就会直接抛出校验失败错误。 - 使用私有CA或者小众CA签发的证书,对应的根证书没有被收录到Dart SDK的内置信任列表中。
- CDN侧配置证书时仅上传了站点的叶子证书,没有拼接配套的中间CA证书,导致服务端返回的证书链不完整。普通浏览器访问这类站点时,会自动通过证书内的AIA扩展地址拉取缺失的中间证书补全链,因此浏览器访问不会报证书错误,但Dart内置的
修复方案
- 服务端规范修复(生产环境推荐):将站点叶子证书、中间CA证书按顺序拼接为完整证书链文件,重新上传到Cloud CDN的证书配置项中,确保服务端TLS握手时返回完整的证书链,无需修改任何客户端代码即可解决问题。
- 客户端临时调试方案(禁止生产环境全量放开):可以通过自定义
HttpOverrides调整证书校验逻辑,临时跳过对应域名的证书校验,或者手动将缺失的根/中间证书加入应用的信任证书列表做证书固定。
临时跳过指定域名证书校验的示例代码:
注意:如果放开所有域名的证书校验,会导致HTTPS请求存在中间人攻击风险,生产环境必须使用服务端修复证书链、或者客户端证书固定的方案。class MyHttpOverrides extends HttpOverrides{ @override HttpClient createHttpClient(SecurityContext? context){ return super.createHttpClient(context) ..badCertificateCallback = (X509Certificate cert, String host, int port)=> host == 'cdn.vaandra.com'; } } void main(){ // App启动前初始化全局Http配置 HttpOverrides.global = MyHttpOverrides(); runApp(const MyApp()); }
内容的提问来源于stack exchange,提问作者xetra11
相关产品推荐
相关产品推荐

