Commit 47569bdfc6342f418825c3a99b054971d3808c8c

Authored by 吴启风
1 parent f66c1d1e

feat:支持按需启用 Whistle 抓包

README.md
1 # Wow English 1 # Wow English
2 2
3 * flutter版本号:3.19.2 3 * flutter版本号:3.19.2
  4 +
  5 +## Android 使用 Whistle 抓包
  6 +
  7 +为了让 Dio 的 HTTPS 请求能够经过 Whistle 解密,Android Debug 包默认允许任意服务器证书,不需要在项目中内置或指定 Whistle Root CA。普通 Release 包仍使用系统默认的严格证书校验。
  8 +
  9 +### 构建方式
  10 +
  11 +Debug 包默认允许 Whistle 抓包:
  12 +
  13 +```bash
  14 +flutter build apk --debug
  15 +```
  16 +
  17 +构建允许 Whistle 抓包的 Release 包:
  18 +
  19 +```bash
  20 +flutter build apk --release --dart-define=ENABLE_WHISTLE_CA=true
  21 +```
  22 +
  23 +构建正常生产 Release 包(不允许 Whistle 抓包):
  24 +
  25 +```bash
  26 +flutter build apk --release
  27 +```
  28 +
  29 +手机通过 Clash Meta 将流量转发到运行 Whistle 的电脑后,即可抓取 Dio 发出的 HTTPS 请求。因为抓包构建不绑定具体 CA,所以更换 Whistle 实例或重新生成 Root CA 后无需修改代码和重新导入证书。
  30 +
  31 +> Debug 包以及带 `ENABLE_WHISTLE_CA=true` 的 Release 包会接受任意 HTTPS 证书,无法抵御中间人攻击。此类 APK 只应在内部测试设备和测试人员之间分发,不能作为正式生产包发布。
lib/common/request/request_client.dart
1 import 'dart:convert'; 1 import 'dart:convert';
  2 +import 'dart:io';
2 3
3 import 'package:dio/dio.dart'; 4 import 'package:dio/dio.dart';
  5 +import 'package:dio/io.dart';
4 import 'package:flutter/foundation.dart'; 6 import 'package:flutter/foundation.dart';
5 import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 7 import 'package:pretty_dio_logger/pretty_dio_logger.dart';
6 import 'package:wow_english/utils/toast_util.dart'; 8 import 'package:wow_english/utils/toast_util.dart';
@@ -17,14 +19,32 @@ part 'apis.dart'; @@ -17,14 +19,32 @@ part 'apis.dart';
17 RequestClient requestClient = RequestClient(); 19 RequestClient requestClient = RequestClient();
18 20
19 class RequestClient { 21 class RequestClient {
  22 + static const bool _enableWhistleCaptureInRelease = bool.fromEnvironment(
  23 + 'ENABLE_WHISTLE_CA',
  24 + defaultValue: false,
  25 + );
  26 +
20 late Dio _dio; 27 late Dio _dio;
21 28
22 RequestClient() { 29 RequestClient() {
23 - _dio = Dio(BaseOptions(baseUrl: RequestConfig.baseUrl, connectTimeout: RequestConfig.connectTimeout)); 30 + _dio = Dio(BaseOptions(
  31 + baseUrl: RequestConfig.baseUrl,
  32 + connectTimeout: RequestConfig.connectTimeout));
24 _dio.interceptors.add(TokenInterceptor()); 33 _dio.interceptors.add(TokenInterceptor());
25 if (kDebugMode) { 34 if (kDebugMode) {
26 - _dio.interceptors  
27 - .add(PrettyDioLogger(requestHeader: true, requestBody: true, responseHeader: true, maxWidth: 120)); 35 + _dio.interceptors.add(PrettyDioLogger(
  36 + requestHeader: true,
  37 + requestBody: true,
  38 + responseHeader: true,
  39 + maxWidth: 120));
  40 + }
  41 +
  42 + // 仅供内部抓包构建使用;启用后会接受任意 HTTPS 证书。
  43 + if (Platform.isAndroid && (kDebugMode || _enableWhistleCaptureInRelease)) {
  44 + _dio.httpClientAdapter = IOHttpClientAdapter(
  45 + createHttpClient: () =>
  46 + HttpClient()..badCertificateCallback = (_, __, ___) => true,
  47 + );
28 } 48 }
29 } 49 }
30 50
@@ -79,7 +99,11 @@ class RequestClient { @@ -79,7 +99,11 @@ class RequestClient {
79 bool Function(ApiException)? onError, 99 bool Function(ApiException)? onError,
80 }) { 100 }) {
81 return request(url, 101 return request(url,
82 - method: 'GET', queryParameters: queryParameters, headers: headers, onResponse: onResponse, onError: onError); 102 + method: 'GET',
  103 + queryParameters: queryParameters,
  104 + headers: headers,
  105 + onResponse: onResponse,
  106 + onError: onError);
83 } 107 }
84 108
85 /// post 109 /// post