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

Flutter使用Dio实现SSL固定抛出HandshakeException问题求助

安全实现Flutter SSL固定的解决方案

问题根源分析

你遇到的首次请求成功、后续请求失败的问题,以及CERTIFICATE_VERIFY_FAILED错误,核心来自三个问题:

  1. 每次调用fetchDataUsingDio都新建Dio实例,重复初始化HttpClient和证书上下文,导致客户端状态冲突
  2. 错误设置badCertificateCallback = false,绕过证书验证的同时和信任证书的逻辑冲突,破坏了SSL验证链
  3. 可能仅导入了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 16:39:21