Flutter for OpenHarmony:dio_cookie_manager 让 Dio 发挥会话管理能力,像浏览器一样自动处理 Cookie 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言

在移动端开发中,我们通常使用 JWT (Authorization Header) 进行身份验证。但如果你的后端是基于 Session/Cookie 的老系统(如 PHP/Java JSP),或者你需要对接网页爬虫,那么 Cookie 的管理就变得至关重要。

dio 本身是不存储 Cookie 的。dio_cookie_manager 是一个官方推荐的拦截器,它结合 cookie_jar 库,能自动从响应头提取 Set-Cookie,并在下次请求时带上 Cookie,完全模拟浏览器的行为。

一、概念介绍/原理解析

1.1 基础概念

  • CookieManager: Dio 的拦截器,负责提取和注入 Cookie。
  • CookieJar: 内存中的 Cookie 存储器(App 重启丢失)。
  • PersistCookieJar: 持久化的 Cookie 存储器(存在文件系统,重启保留)。

请求前拦截
加载 Cookie
注入请求头
发送
带有 Set-Cookie
保存 Cookie
写入存储
Dio 请求
CookieManager
CookieJar
服务端
Dio 响应

1.2 进阶概念

它可以处理 Cookie 的 Path, Domain, Secure, HttpOnly 属性,确保 cookie 不会泄露给错误的域名。还可以通过自定义 Storage 实现将 Cookie 存入加密数据库。

二、核心 API/组件详解

2.1 依赖安装

yaml 复制代码
dependencies:
  dio: ^5.0.0
  cookie_jar: ^4.0.0
  dio_cookie_manager: ^3.0.0
  path_provider: ^2.0.0 # 用于持久化

2.2 基础用法(内存模式)

dart 复制代码
import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:cookie_jar/cookie_jar.dart';

void main() async {
  final dio = Dio();
  final cookieJar = CookieJar();
  
  // 注入拦截器
  dio.interceptors.add(CookieManager(cookieJar));

  // 第一次请求:登录,服务端 Set-Cookie: session=123
  await dio.post('https://api.example.com/login');

  // 第二次请求:自动带上 Cookie: session=123
  await dio.get('https://api.example.com/user/profile');
}

2.3 持久化(重启不掉线)

dart 复制代码
import 'package:path_provider/path_provider.dart';

void initDio() async {
  // 获取应用文档目录
  final appDocDir = await getApplicationDocumentsDirectory();
  
  // 创建持久化 Jar
  final persistJar = PersistCookieJar(
    storage: FileStorage("${appDocDir.path}/.cookies/"),
  );

  dio.interceptors.add(CookieManager(persistJar));
}

三、常见应用场景

3.1 场景 1:SSO 单点登录

对接企业内部的老旧 SSO 系统,需要多次重定向并携带 Cookie 才能获取最终 Token。

dart 复制代码
// CookieManager 会自动处理重定向过程中的 Cookie 变化
await dio.get('https://sso.company.com/auth?service=myapp');

3.2 场景 2:WebView 同步

用户在 WebView 中登录了,App 原生请求也需要共享该登录状态。

dart 复制代码
// 手动提取 WebView 的 Cookie 并存入 Jar
final cookies = await webview.getCookies();
await cookieJar.saveFromResponse(Uri.parse(url), cookies);

3.3 场景 3:爬虫与自动化测试

模拟浏览器行为,抓取需要登录才能访问的网页数据。

dart 复制代码
// 伪造 User-Agent 和 Cookie
dio.options.headers['User-Agent'] = 'Mozilla/5.0...';
await dio.get('https://www.zhihu.com');

四、OpenHarmony 平台适配

4.1 文件路径

path_provider 已完整适配 OpenHarmony,通过它获取的 ApplicationDocumentsDirectory 是完全符合鸿蒙沙箱规范的,因此 PersistCookieJar 可以放心使用。

4.2 跨域与安全

虽然 Dio 不是浏览器,不受同源策略限制,但鸿蒙系统的网络权限管理通过 module.json5 控制。如果 Cookie 中包含 HttpOnly,dio_cookie_manager 依然能处理(因为它是在 Dart 层模拟),但要注意不要将敏感 Cookie 打印到日志中。

五、完整示例代码

本示例展示在鸿蒙应用中实现一个"记住登录状态"的功能。首次登录后,即使重启 App,Cookie 依然存在,无需再次登录。

dart 复制代码
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:path_provider/path_provider.dart';

// 模拟 API
const String _loginUrl = 'https://httpbin.org/cookies/set/session/abcdefg';
const String _profileUrl = 'https://httpbin.org/cookies';

void main() {
  runApp(const MaterialApp(home: CookiePage()));
}

class CookiePage extends StatefulWidget {
  const CookiePage({super.key});

  @override
  State<CookiePage> createState() => _CookiePageState();
}

class _CookiePageState extends State<CookiePage> {
  String _status = '未初始化';
  late Dio _dio;
  PersistCookieJar? _cookieJar;

  @override
  void initState() {
    super.initState();
    _initDio();
  }

  Future<void> _initDio() async {
    try {
      final dir = await getApplicationDocumentsDirectory();
      // 在 OpenHarmony 沙箱目录下存储 Cookie
      _cookieJar = PersistCookieJar(storage: FileStorage("${dir.path}/cookies"));
      
      _dio = Dio();
      _dio.interceptors.add(CookieManager(_cookieJar!));
      
      setState(() => _status = '已就绪 (路径: ${dir.path})');
    } catch (e) {
      setState(() => _status = '初始化失败: $e');
    }
  }

  Future<void> _login() async {
    if (_cookieJar == null) return;
    try {
      setState(() => _status = '正在登录...');
      // 访问此 URL 会使服务器设置 Set-Cookie
      await _dio.get(_loginUrl);
      setState(() => _status = '登录成功,Cookie 已保存');
    } catch (e) {
      setState(() => _status = '登录错误: $e');
    }
  }

  Future<void> _checkProfile() async {
    if (_cookieJar == null) return;
    try {
      setState(() => _status = '正在检查会话...');
      // 访问此 URL 会返回当前请求带的所有 Cookie
      final response = await _dio.get(_profileUrl);
      setState(() => _status = 'API 返回:\n${response.data}');
    } catch (e) {
      setState(() => _status = '请求错误: $e');
    }
  }

  Future<void> _logout() async {
    if (_cookieJar == null) return;
    // 清除所有 Cookie
    await _cookieJar!.deleteAll();
    setState(() => _status = '已退出登录 (Cookie 清除)');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Dio Cookie 示例')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Text('状态: $_status', style: const TextStyle(fontSize: 16)),
            const SizedBox(height: 20),
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  ElevatedButton(onPressed: _login, child: const Text('模拟登录')),
                  const SizedBox(width: 10),
                  ElevatedButton(onPressed: _checkProfile, child: const Text('检查 Session')),
                  const SizedBox(width: 10),
                  OutlinedButton(onPressed: _logout, child: const Text('清除 Cookie')),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

六、总结

dio_cookie_manager 是对接基于 Session 的旧系统的唯一正确姿势。

最佳实践

  1. 单例模式PersistCookieJar 涉及到文件锁,建议全局只创建一个实例,否则可能出现读写冲突。
  2. 清理策略 :虽然 Session 会过期,但持久化的 Cookie 文件却不会自动删除。可以在 App 启动时调用 .deleteExpired() 来清理过期的 Cookie。
相关推荐
大树888 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠9 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
程序猿追9 小时前
那个右下角的小数字怎么“卡”住我打字——我用 HarmonyOS 自己写了一个字数限制输入框
pytorch·华为·harmonyos
古德new9 小时前
鸿蒙PC使用electron迁移:Joplin Electron 桌面适配全记录
华为·electron·harmonyos
世人万千丶9 小时前
桌面便签小应用 - HarmonyOS ArkUI 开发实战-TextArea与Flex布局-PC版本
华为·harmonyos·鸿蒙·鸿蒙系统
霸道流氓气质9 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧海灵舟9 小时前
AGenUI 鸿蒙端实战踩坑录:从 Column 布局消失到异步组件宽度为 0
华为·harmonyos
bush49 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5209 小时前
Linux 11 动态监控指令top
linux
yuegu7779 小时前
HarmonyOS应用<节气通>开发第33篇:状态管理实战
华为·harmonyos