Commit 47569bdfc6342f418825c3a99b054971d3808c8c
1 parent
f66c1d1e
feat:支持按需启用 Whistle 抓包
Showing
2 changed files
with
56 additions
and
4 deletions
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 |