Flutter使用Dio实现SSL固定抛出HandshakeException问题求助
安全实现Flutter SSL固定的解决方案
问题根源分析
你遇到的首次请求成功、后续请求失败的问题,以及CERTIFICATE_VERIFY_FAILED错误,核心来自三个问题:
- 每次调用
fetchDataUsingDio都新建Dio实例,重复初始化HttpClient和证书上下文,导致客户端状态冲突 - 错误设置
badCertificateCallback = false,绕过证书验证的同时和信任证书的逻辑冲突,破坏了SSL验证链 - 可能仅导入了Facebook的服务器叶子证书,缺少中间/根证书,导致验证链不完整
正确的Dio实现方案(推荐)
采用全局单例Dio实例,仅初始化一次证书配置,避免重复加载和上下文冲突,同时严格保留证书验证逻辑:
1. 准备完整的信任证书
优先使用Facebook的根CA证书(有效期更长,无需频繁更新);如果使用叶子证书,需将中间证书、根证书合并到同一个PEM文件中。
2. 全局Dio客户端封装
import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class DioClient { static final Dio _dio = Dio(); static bool _isInitialized = false; static Future<void> init() async { if (_isInitialized) return; // 加载assets中的证书文件 final ByteData certBytes = await rootBundle.load('assets/certificates/fb_root.pem'); final Uint8List certUint8List = certBytes.buffer.asUint8List(); // 配置HttpClient适配器,仅初始化一次 (_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { final securityContext = SecurityContext(); // 添加信任的证书字节 securityContext.setTrustedCertificatesBytes(certUint8List); // 不设置badCertificateCallback,让系统自动完成证书验证 return HttpClient(context: securityContext); }; _isInitialized = true; } // 获取全局Dio实例 static Dio get instance => _dio; }
3. 初始化全局客户端
在main函数中提前完成初始化:
void main() async { WidgetsFlutterBinding.ensureInitialized(); await DioClient.init(); runApp(const MyApp()); }
4. 发起网络请求
void fetchData() async { try { final response = await DioClient.instance.get('https://www.facebook.com/'); print(response.data); } on DioError catch (e) { print('请求错误: ${e.message}'); } catch (e) { print('未知错误'); } }
HttpOverrides全局实现方案
如果需要让所有网络请求(包括第三方库的请求)都应用SSL固定,可使用HttpOverrides:
1. 自定义HttpOverrides类
import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; class SSLPinningHttpOverrides extends HttpOverrides { final Uint8List _certBytes; SSLPinningHttpOverrides(this._certBytes); @override HttpClient createHttpClient(SecurityContext? context) { final securityContext = SecurityContext(); securityContext.setTrustedCertificatesBytes(_certBytes); final client = HttpClient(context: securityContext); // 禁止绕过证书验证,确保SSL固定生效 client.badCertificateCallback = null; return client; } }
2. 初始化全局配置
Future<void> initSSLPinning() async { final ByteData certBytes = await rootBundle.load('assets/certificates/fb_root.pem'); HttpOverrides.global = SSLPinningHttpOverrides(certBytes.buffer.asUint8List()); }
3. 调用初始化(在main函数中)
void main() async { WidgetsFlutterBinding.ensureInitialized(); await initSSLPinning(); runApp(const MyApp()); }
关键安全注意事项
- 绝对不要设置
badCertificateCallback = (_,__,___) => false:这会完全跳过证书验证,等于放弃SSL固定的安全意义,任何恶意证书都能通过验证 - 优先使用根CA证书:根证书有效期通常为10-20年,而服务器叶子证书仅1-2年,能减少证书更新频率
- 确保证书格式正确:必须是PEM格式,合并多证书时需按顺序(叶子→中间→根)排列
- 全局初始化仅执行一次:重复创建客户端或加载证书会导致上下文冲突,是后续请求失败的核心原因
内容的提问来源于stack exchange,提问作者Navin Kumar
相关产品推荐
相关产品推荐

