Flutter配置代理抓包

1,272 阅读7分钟

我们在做应用开发的时候,经常需要利用一些工具来抓取网络请求接口,这样可以极大的方便接口联调。在使用 Flutter 做应用开发时就需要用到 Charles 这个抓包工具。

1. 抓包工具 Charles

我们到Charles官网下载:

image.png

1.1 Charles 破解

Charles 是收费软件,首次打开会提示你,可以免费试用30天,但是每次使用时间不能超过30分钟,并且启动时将会有10秒的延时。

开Charles破解网站,输入你当前下载的 Charles 的版本号,点击生成,如下图显示:

image.png

这时候会跳出弹框,复制一下 License Key的值:

image.png

打开 Charles 工具后,点击 Help——Registered Name,跳出弹框输入版本号和 License Key 的值,点击 Register:

image.png

然后重启 Charles 即可。

1.2. Charles 抓包设置

1.2.1 配置 Charles 根证书

首先打开 Charles,help -> SSL Proxying -> install charles root certificate

image.png

之后会弹出钥匙串,如果没有弹出,请自行打开,如下图:

image.png

系统默认是不信任 Charles 的证书的,此时对不信任的证书右键,在弹出的下拉菜单中选择『显示简介』,点击使用此证书时,把使用系统默认改为始终信任,如下图:

image.png

然后关掉当前弹框系统会提示了输入密码,输入或者指纹验证后,出现下图所示即说明 charles 的证书已经被信任了:

image.png

还有钥匙串的系统的证书也需要同上述一样的操作选择信任:

image.png

image.png

1.2.2 Proxy Settings 代理设置

设置代理暴露端口:

image.png

image.png

1.2.3 SSL Proxying Settings(https url 抓包设置)

image.png

image.png

一般来说 配置 *.80和 *.443就可以了,但是有的可能不是443 直接配置 *

image.png

配置*号,即可以抓取ssl/https所有端口信息,然后已经可以抓取mac端的包了。

1.2.4 其他配置

  1. 如果你想过滤掉无关自身需求的站点,只想看单独某个站点,最简便的做法就是直接在 Charles 左下角过滤框中输入单独想看的站点请求:

image.png

如果你想看某几个站点的,按照下面操作:

image.png

image.png

然后,你就可以在列表中查看到添加过的网址,想要继续添加别的站点继续点击 Add 即可。

image.png

  1. 修改服务器返回内容,mock 模拟假数据,勾选Enable Map Local ,这样和后端调试的时候就不用等后端接口都完成才能调试了,只要有数据格式即可,调试的时候大有用处。对于一些平常测试不到的情况,异常情况等都可以随意模拟修改,这个功能很实用。

image.png

image.png

只需要本地创建一个.json文件,写 json 数据和接口关联起来即可。

1.3 移动端配置

我们查看Charles的 help,按照下图所示来操作:

image.png

这里,我们可以看到一个弹框,告诉了你怎么给手机设置代理:

image.png

ps: 一定要让电脑和手机都在同一个局域网中(或是同一个WI-FI当中)

我这里以 iphone 为例,打开手机的设置,按照如下操作进行即可:

image.png

image.png

image.png

将上述 Charies 弹框中所列出的你的电脑的 ip 地址和端口号填写到下图:

image.png

配置完成后,Charles 会出现如下图所示弹窗,点击 Allow 允许即可:

image.png

然后打开手机上的safari浏览器输入 chls.pro/ssl 网址,会下载一个 pem 格式的文件,要将其后缀 pem 改为 crt。如果你的手机浏览器无法安装,也可以在电脑上进入该网页下载安装,然后通过隔空投送到iphone上。

image.png

image.png

点击安装,开始认证:

image.png

image.png

然后回到手机设置页面,点击通用 ——> 关于本机 ——> 证书信任设置:

image.png

至此,Charles 就可以抓包移动端接口数据了。

2. 配置 Flutter 代理

完成工具准备后,由于 Flutter 默认不走系统代理,所以我们还需要手动在 Flutter 项目中配置代理,这样 Charles 才能正确抓到包。

这里提供两种方案,一种是在请求库的配置里设置代理,另一种是利用 Flutter 原生的类来完成。

2.1 http 请求库配置代理

2.1.1 dio 配置代理

我们希望有一个设置代理的页面,让开发者在页面上输入代理。这里我构建了一个简单点的设置代理的UI页面:

class ProxySettingPage extends StatefulWidget {
  const ProxySettingPage({Key? key}) : super(key: key);

  @override
  State<ProxySettingPage> createState() => _ProxySettingPageState();
}

class _ProxySettingPageState extends State<ProxySettingPage> {
  late bool _isOn; // 是否开启代理
  final TextEditingController _controller = TextEditingController(); // TextEditingController 实例,用于控制和获取 TextField 的内容

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomNavigatorBar(title: 'PROXY'),
      body: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: const InputDecoration(
              hintText: '172.168.0.1',
            ),
          ),
          CupertinoSwitch( // switch开关组件,用户可以切换代理的开关状态。当状态改变时,调用 _action 方法更新设置。
            value: _isOn,
            onChanged: _action
          )
        ],
      ),
    );
  }
}

初始化时,我们需要从 Cookies 中获取代理设置的状态和 IP 地址,并填入文本框:

@override
void initState() {
  super.initState();
  _isOn = Cookies.getBool('xx_proxy_on') ?? false;
  _controller.text = Cookies.getString('xx_proxy_ip') ?? '';
}

接下来开始写 _action 方法,处理代理开关状态的变化:

  1. 根据 isOn 的值,更新 Cookies 中的代理状态和 IP 地址。

  2. 在 Flutter 项目中通常用 dio 库做 http 请求,我们可以通过 dio 的httpClientAdapter属性配置我们的本地代理(DefaultHttpClientAdapter 提供了一个onHttpClientCreate 回调来设置底层 HttpClient的代理)。

  3. 更新 Http().dio 为新的 Dio 实例,并设置自定义代理配置。

_action(bool isOn) {
  _isOn = isOn;
  Cookies.putBool('xx_proxy_on', isOn);
  Cookies.putString('xx_proxy_ip', _controller.text);
  FocusManager.instance.primaryFocus?.unfocus();
  
  Dio oldDio = Http().dio; // 从 Http 服务中获取当前的 Dio 实例, 并将其保存到 oldDio 变量中
  Dio dio = Dio(oldDio.options); // 通过 oldDio.options 创建一个新的 Dio 实例, 它继承了之前的 Dio 配置,如超时时间、请求头等
  dio.interceptors.addAll(oldDio.interceptors); // 将旧 Dio 实例的所有拦截器添加到新的 Dio 实例中。这些拦截器可以用于请求和响应的拦截与处理,比如日志记录、错误处理
  (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) { // 将新的 dio 实例的 httpClientAdapter 强制转换为 DefaultHttpClientAdapter,并设置 onHttpClientCreate 回调。这允许我们对创建的 HttpClient 进行自定义配置
    client.findProxy = (url) { // 在 findProxy 回调中,根据 isOn 的值决定是否启用代理
      if (!isOn) { // 如果 isOn 为 false,返回 'DIRECT',表示直接连接,不使用代理
        return 'DIRECT';
      }
      String ip = Cookies.getString('xx_proxy_ip') ?? '';
      return 'PROXY $ip:8888'; // 如果 isOn 为 true,从 Cookies 中获取代理 IP 地址,并返回 `PROXY $ip:8888`,表示使用指定的代理服务器和端口
    };
    client.userAgent = null; // 清除 userAgent 设置。如果之前有自定义的用户代理字符串,这里将其设置为 null
    client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; // 设置一个回调函数来处理无效证书。在这里,所有的证书都被接受,无论其是否有效,这通常用于开发环境中测试,但不推荐在生产环境中使用
    return client;
  };
  Http().dio = dio; // 更新全局 Dio 实例。以后所有通过 Http().dio 进行的请求都会使用新的代理设置
  setState(() {});
}

其中client.findProxy函数用来返回我们的代理接口,Charles 的默认的系统代理端口是8888,所以这里配置成 PROXY $ip:8888 就可以了。

Flutter 请求 https 时用的是自己的CA认证证书,所以 Charles 在认证证书时会通不过证书校验,从而导致抓包错误。故而我们直接通过 client.badCertificateCallback 函数返回 true 来忽略证书就好了。

当然,在有些实际项目中,不会单独写个页面让开发者手填代理,而是将代理抽取到配置文件中,只要你在代码中手动添加/修改你的代理即可,如下图所示:

image.png

ps:一般情况下,开发者需要填写的代理都是个人电脑 ip 地址,我们除了可以通过在终端输入命令ipconfig来查看 ip 外,还可以通过 Charles 来查看,如图所示:

image.png

2.1.2 web_socket_channel 配置代理

如果你的项目中需要抓取 websocket, 那么在有了上述的 dio 配置后还不够,这时候我们的 websocketb 请求还是无法抓包,我们需要借助 web_socket_channel 这个库。

因为官方的 web_socket_channel 还不支持代理,所以我们不能直接从官网拉取这个库。我们可以改成以下方式进行安装:

dependencies:
  web_socket_channel:
      git:
        url: https://github.com/IFreeOvO/web_socket_channel.git
        ref: master

基于上述 dio 配置的思路,我们来开始配置 web_socket_channel

import 'package:web_socket_channel/io.dart';

// 创建一个自己的 HttpClient 对象
SecurityContext ctx = SecurityContext.defaultContext; // SecurityContext.defaultContext 返回默认的安全上下文 (SecurityContext) 实例。这个上下文用于配置与 SSL/TLS 相关的安全设置,比如证书验证
HttpClient client = HttpClient(context: ctx) // 创建一个 HttpClient 实例,并将之前获取的 SecurityContext (ctx) 传递给它。这使得 HttpClient 使用该上下文进行安全通信
  ..findProxy = ((url) { // 使用级联操作符 `..` 来设置 HttpClient 的 findProxy 属性
    String ip = Cookies.getString('xx_proxy_ip') ?? '';
    return 'PROXY $ip:8888';
  })
  ..badCertificateCallback = (cert, host, port) { // 使用级联操作符 `..` 设置 HttpClient 的 badCertificateCallback
  return true;
};

_channel = IOWebSocketChannel.connect( // 使用 IOWebSocketChannel 的 connect 方法创建一个 WebSocket 连接
  'wss://xxx.com',
  customClient: client, // 指定使用前面创建的自定义 HttpClient
);

2.2 重写原生方法

在入口文件main.dart中,我们需要定义一个HttpOverrides的子类,重写它的createHttpClient方法。原理也是一样的,把findProxybadCertificateCallback方法进行替换,然后挂载到全局。

// 重写 HttpOverrides
class MyHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    var http = super.createHttpClient(context);
    http.findProxy = (url) {
      return 'PROXY localhost:8888'; // 将 localhost 改成具体的 ip 地址
    };
    http.badCertificateCallback =
        (X509Certificate cert, String host, int port) => true;
    return http;
  }
}

void main() {
  HttpOverrides.global = MyHttpOverrides(); // 应用中的所有 HTTP 请求都使用自定义的 HttpClient 设置
  runApp(MyApp());
}

这种方案的好处是不受第三方请求库限制,配置完后httpswebsocket都能正确抓包。