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

Flutter中WebSocket连接证书验证失败问题解决方案咨询

Flutter WebSocket自签名证书验证失败解决方案

问题根源分析

你当前的代码存在两个核心问题:

  1. 自定义的HttpClient创建后未实际传递给IOWebSocketChannel,导致WebSocket握手仍使用系统默认客户端,证书校验规则未生效
  2. HttpOverrides.global = null在finally块中过早执行,此时WebSocket握手可能尚未完成,全局覆盖被提前重置

解决方案1:正确传递自定义HttpClient给WebSocket

修改IOWebSocketChannel.connect调用,通过customClient参数传入你配置了badCertificateCallback的HttpClient:

Future<List<Acumulado>?> _connectToWebSocket(String id) async {
  Completer<List<Acumulado>> completer = Completer();
  List<Acumulado> maquinas = [];

  try {
    final String webSocketUrl =
        '${widget.constantes.appSettings!.address.replaceFirst("http", "ws")}:8887/${widget.nombrebd}';

    // 创建自定义HTTP客户端以接受自签名证书
    final httpClient = HttpClient()
      ..badCertificateCallback =
          (X509Certificate cert, String host, int port) => true;

    // 关键:将自定义HttpClient传入WebSocket连接
    _channel = IOWebSocketChannel.connect(
      Uri.parse(webSocketUrl),
      headers: {'session': widget.constantes.session, 'authenticate': 'true'},
      customClient: httpClient, // 新增这一行
    );

    _channel.sink.add('{"clase":"request","datos":[{}]}');
    _channel.stream.listen((message) {
      var data = json.decode(message);
      print("data: $data $message");
      if (data['clase'] == 'estado') {
        var message = data['datos'][0]['mensaje'];
        print(message);
        maquinas = (data['datos']
            .map<Acumulado>((item) => Acumulado.fromJson(item))
            .toList());
        _channel.sink.add('{"clase":"request","datos":[{}]}');
        completer.complete(maquinas);
      }
    });
  } catch (e) {
    print('Error connecting to WebSocket: $e');
    completer.completeError(e);
  }

  return completer.future;
}

解决方案2:正确使用HttpOverrides全局覆盖

如果选择全局禁用证书校验,需在WebSocket握手完成前保持HttpOverrides生效,不要在finally中过早重置:

  1. 在应用启动时全局设置(推荐,避免局部异步时序问题):
void main() {
  HttpOverrides.global = MyHttpOverrides();
  runApp(const YourApp());
}
  1. 若仅需局部生效,需确保在WebSocket连接完成后再重置覆盖:
Future<List<Acumulado>?> _connectToWebSocket(String id) async {
  Completer<List<Acumulado>> completer = Completer();
  List<Acumulado> maquinas = [];
  // 提前设置全局覆盖
  HttpOverrides.global = MyHttpOverrides();

  try {
    final String webSocketUrl =
        '${widget.constantes.appSettings!.address.replaceFirst("http", "ws")}:8887/${widget.nombrebd}';

    _channel = IOWebSocketChannel.connect(
      Uri.parse(webSocketUrl),
      headers: {'session': widget.constantes.session, 'authenticate': 'true'},
    );

    _channel.sink.add('{"clase":"request","datos":[{}]}');
    _channel.stream.listen((message) {
      var data = json.decode(message);
      print("data: $data $message");
      if (data['clase'] == 'estado') {
        var message = data['datos'][0]['mensaje'];
        print(message);
        maquinas = (data['datos']
            .map<Acumulado>((item) => Acumulado.fromJson(item))
            .toList());
        _channel.sink.add('{"clase":"request","datos":[{}]}');
        completer.complete(maquinas);
        // 连接完成后再重置覆盖(可选)
        HttpOverrides.global = null;
      }
    });
  } catch (e) {
    print('Error connecting to WebSocket: $e');
    completer.completeError(e);
    HttpOverrides.global = null;
  }

  return completer.future;
}

解决方案3:信任特定自签名证书(更安全)

完全禁用证书校验存在安全风险,建议将服务器的自签名证书导入应用,仅信任该证书:

  1. 将证书文件(如server_cert.pem)放入assets目录,并在pubspec.yaml中声明:
assets:
  - assets/server_cert.pem
  1. 加载证书并配置SecurityContext:
Future<List<Acumulado>?> _connectToWebSocket(String id) async {
  Completer<List<Acumulado>> completer = Completer();
  List<Acumulado> maquinas = [];

  try {
    final String webSocketUrl =
        '${widget.constantes.appSettings!.address.replaceFirst("http", "ws")}:8887/${widget.nombrebd}';

    // 加载自签名证书
    ByteData certData = await rootBundle.load('assets/server_cert.pem');
    SecurityContext securityContext = SecurityContext.defaultContext;
    securityContext.setTrustedCertificatesBytes(certData.buffer.asUint8List());

    // 使用带信任证书的HttpClient
    final httpClient = HttpClient(context: securityContext);
    _channel = IOWebSocketChannel.connect(
      Uri.parse(webSocketUrl),
      headers: {'session': widget.constantes.session, 'authenticate': 'true'},
      customClient: httpClient,
    );

    // 后续监听逻辑不变...
    _channel.sink.add('{"clase":"request","datos":[{}]}');
    _channel.stream.listen((message) {
      var data = json.decode(message);
      print("data: $data $message");
      if (data['clase'] == 'estado') {
        var message = data['datos'][0]['mensaje'];
        print(message);
        maquinas = (data['datos']
            .map<Acumulado>((item) => Acumulado.fromJson(item))
            .toList());
        _channel.sink.add('{"clase":"request","datos":[{}]}');
        completer.complete(maquinas);
      }
    });
  } catch (e) {
    print('Error connecting to WebSocket: $e');
    completer.completeError(e);
  }

  return completer.future;
}

内容的提问来源于stack exchange,提问作者yellowman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 20:39:51