Commit 47569bdfc6342f418825c3a99b054971d3808c8c
1 parent
f66c1d1e
feat:支持按需启用 Whistle 抓包
Showing
2 changed files
with
56 additions
and
4 deletions
README.md
| 1 | 1 | # Wow English |
| 2 | 2 | |
| 3 | 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 | 1 | import 'dart:convert'; |
| 2 | +import 'dart:io'; | |
| 2 | 3 | |
| 3 | 4 | import 'package:dio/dio.dart'; |
| 5 | +import 'package:dio/io.dart'; | |
| 4 | 6 | import 'package:flutter/foundation.dart'; |
| 5 | 7 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; |
| 6 | 8 | import 'package:wow_english/utils/toast_util.dart'; |
| ... | ... | @@ -17,14 +19,32 @@ part 'apis.dart'; |
| 17 | 19 | RequestClient requestClient = RequestClient(); |
| 18 | 20 | |
| 19 | 21 | class RequestClient { |
| 22 | + static const bool _enableWhistleCaptureInRelease = bool.fromEnvironment( | |
| 23 | + 'ENABLE_WHISTLE_CA', | |
| 24 | + defaultValue: false, | |
| 25 | + ); | |
| 26 | + | |
| 20 | 27 | late Dio _dio; |
| 21 | 28 | |
| 22 | 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 | 33 | _dio.interceptors.add(TokenInterceptor()); |
| 25 | 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 | 99 | bool Function(ApiException)? onError, |
| 80 | 100 | }) { |
| 81 | 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 | 109 | /// post | ... | ... |