Flutter for OpenHarmony: Flutter 三方库 fresh 自动处理 OAuth2 令牌刷新(无感登录续期)
开源鸿蒙跨平台社区推出fresh库,实现Token无感刷新功能,解决移动应用频繁登出问题。该库将Token的存储、读取、验证和刷新解耦,支持自定义令牌模型和并发刷新锁机制。通过拦截401错误自动触发刷新流程,并提供了状态流管理,可适配OpenHarmony多进程场景。实战示例展示了如何配置核心实例并实现高可靠性认证,最终达到用户无感知的流畅体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言
在开发 OpenHarmony 移动应用时, Access Token 通常具有较短的有效期(如 3600s)。一旦过期,直接报错会导致用户请求失败,频繁登出,体验极差。
fresh 库提供了“无感刷新”的抽象模型。它将 Token 的存储、读取、验证、刷新这四个环节高度解耦,让开发者能像配置乐高积木一样搭建认证系统。
一、核心工作流程
二、核心 API 实战
2.1 定义带生命周期的 Token
一个严谨的令牌模型应包含 expiresAt,以便客户端判定是否需要主动刷新,并方便 UI 展示倒计时。
class MyToken {
final String accessToken;
final String refreshToken;
final DateTime expiresAt; // 💡 增加过期时间字段
MyToken({
required this.accessToken,
required this.refreshToken,
required this.expiresAt
});
// 💡 辅助属性:判定是否已失效
bool get isExpired => DateTime.now().isAfter(expiresAt);
}

2.2 配置 Fresh 核心实例
final fresh = Fresh<MyToken>(
tokenHeader: (token) => {'Authorization': 'Bearer ${token.accessToken}'},
tokenStorage: InMemoryTokenStorage<MyToken>(), // 💡 建议对接鸿蒙持久化首选项
refreshToken: (token, client) async {
// 💡 无感转化的核心:在这里调用后端刷新 API
print("🌱 正在申请换发新令牌...");
final response = await client.post('/auth/refresh', data: {'refresh': token?.refreshToken});
return MyToken(
accessToken: response.data['token'],
refreshToken: response.data['refresh'],
expiresAt: DateTime.now().add(const Duration(hours: 1)), // 模拟 1 小时有效期
);
},
);

2.3 身份验证状态流 (authenticationStatus)
Fresh 提供了响应式的状态流,在鸿蒙上可极大简化路由逻辑:
- authenticated: 已登录,进入主页。
- unauthenticated: 未登录或刷新失败,跳转登录。

三、OpenHarmony 平台适配
3.1 跨进程 Token 共享
在鸿蒙的元服务或多 HAP 环境下,建议利用 PersistentStore 或 CommonEventManager 广播 Token 变更,虽然 Fresh 基库是 Dart 层的,但其 tokenStorage 接口是完美的挂载点。
3.2 刷新锁机制 (Refresh Lock)
fresh 内部实现了“并发竞争锁”。当鸿蒙应用同时发起 10 个业务请求遇到过期时,它能保证仅触发 1 次刷新请求,其余 9 个请求会自动挂载到等待队列中,待刷新完成后带上新 Token 批量重放。
四、完整实战示例:鸿蒙无感登录拦截器
本示例展示了如何实现一个高可靠性的认证管理页面,并模拟完整的刷新闭环。
import 'package:flutter/material.dart';
import 'package:fresh_dio/fresh_dio.dart';
class FreshBasicDemo extends StatefulWidget {
_FreshBasicDemoState createState() => _FreshBasicDemoState();
}
class _FreshBasicDemoState extends State<FreshBasicDemo> {
late Fresh<MyToken> _fresh;
final List<String> _logs = [];
void initState() {
super.initState();
// 1. 初始化 Fresh
_fresh = Fresh<MyToken>(
tokenHeader: (token) => {'Authorization': 'Bearer ${token.accessToken}'},
tokenStorage: InMemoryTokenStorage<MyToken>(),
refreshToken: (token, client) async {
_addLog("🌱 正在通过 Refresh Token 获取新令牌...");
await Future.delayed(Duration(seconds: 1)); // 模拟网络延迟
return MyToken("new_access_token", "new_refresh_token");
},
);
}
void _addLog(String msg) => setState(() => _logs.insert(0, msg));
void _simulateExpiredRequest() async {
_addLog("📡 发送业务请求...");
if (await _fresh.token == null) {
_addLog("❌ 请求失败:请先点击登录");
return;
}
_addLog("💡 模拟业务请求返回 401... Fresh 拦截器介入");
// 💡 模拟无感刷新流程
try {
final currentToken = await _fresh.token;
// 注意:实际项目中此步骤由拦截器内部自动触发
final newToken = await _fresh.refreshToken!(currentToken, null);
await _fresh.setToken(newToken);
_addLog("✅ 令牌刷新成功!");
_addLog("▶️ 自动重试原业务请求... ✨ 请求重试成功!");
} catch (e) {
_addLog("❌ 刷新失败: $e");
}
}
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () async {
await _fresh.setToken(MyToken("initial_access", "initial_refresh"));
_addLog("👤 模拟登录成功");
},
child: Text("1. 模拟登录"),
),
ElevatedButton(
onPressed: _simulateExpiredRequest,
child: Text("2. 模拟过期请求 (触发无感刷新)"),
),
Expanded(
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, i) => Text(_logs[i]),
),
),
],
);
}
}

五、总结
fresh 解决了身分验证中最繁琐的状态流转问题。通过将 TokenStorage 与鸿蒙原生存储绑定,你可以构建出具有“持久化登录”能力的工业级 App。
更多推荐



所有评论(0)