Flutter 三方库 flutter_secure_storage 的鸿蒙化适配指南
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
前言
在移动应用开发中,敏感数据的安全存储始终是开发者必须面对的核心议题之一。无论是用户登录凭证、API Token,还是加密密钥一旦泄露,都可能导致严重的隐私侵犯和安全隐患。对于 OpenHarmony(以下简称 OH)平台而言,由于其独特的安全架构和应用生态特性,如何在 Flutter 跨平台框架下实现安全可靠的数据存储,成为众多开发者关注的问题。
本文将以 flutter_secure_storage 库为切入点,详细探讨该库在 OpenHarmony 平台上的适配实践。我们将深入剖析 OH 安全区的技术原理,对比分析 flutter_secure_storage 与 hive + encryption 两种方案的技术差异,并通过完整的代码示例指导读者在鸿蒙设备上实现生产级别的敏感数据存储方案。
一、敏感数据存储的技术背景
1.1 移动应用面临的安全挑战
在传统的移动应用开发中,敏感数据存储面临多重安全挑战。首先,明文存储是最常见也是最危险的做法直接将用户密码、Token 等敏感信息以明文形式写入本地存储文件或 SharedPreferences,一旦设备被攻击者获取,敏感数据将完全暴露。其次,简单的可逆加密虽然增加了攻击难度,但由于密钥管理不当或加密算法选用不当,往往难以提供真正的安全保障。此外,缺少硬件级密钥保护机制也是一大隐患即使应用使用了加密算法,加密密钥本身仍然存储在软件层面,容易被提取和利用。
OpenHarmony 作为面向全场景的分布式操作系统,在安全架构设计上有着更为严格的考量。OH 引入了安全区(Secure Area)概念,基于 TEE(Trusted Execution Environment,可信执行环境)技术实现硬件级的安全隔离,这为敏感数据保护提供了更强的底层支撑。
1.2 OpenHarmony 安全区架构解析
理解 OH 安全区架构对于正确使用 flutter_secure_storage 至关重要。OpenHarmony 的安全存储体系可以划分为两个层次:REE(Rich Execution Environment,富执行环境)和 TEE(Trusted Execution Environment,可信执行环境)。
REE 是普通应用的运行环境,运行在主操作系统之上,应用代码和数据存储在这个区域。在 REE 环境下,数据虽然可以通过文件系统进行保护,但密钥本身也存储在软件层面,一旦设备被 Root 或存在漏洞,攻击者可能获取密钥并解密数据。
TEE 则是独立于主系统的安全隔离区域,拥有独立的硬件资源和安全存储空间。TEE 中的加密密钥存储在硬件安全模块中,即使 REE 被完全攻破,攻击者也无法访问存储在 TEE 中的密钥。这种硬件级的密钥保护机制是当前移动安全领域的最佳实践。
flutter_secure_storage 在 OpenHarmony 上的实现,正是基于这一架构。当应用调用安全存储接口时,数据加密操作发生在 TEE 环境中,密钥由硬件安全模块生成和管理,确保了敏感数据的端到端安全。
二、flutter_secure_storage 技术特性分析
2.1 库的核心能力
flutter_secure_storage 是 Flutter 生态中最广泛使用的安全存储库之一,其核心设计理念是为跨平台应用提供统一的安全存储接口。该库在 Android 平台基于 Android Keystore 实现,在 iOS 平台基于 Keychain 实现,而在 OpenHarmony 平台则基于 OH 安全区(Secure Area)实现。
从功能特性来看,flutter_secure_storage 提供的能力包括:加密存储基础键值对数据、支持 AES-256-GCM 加密算法、实现硬件级密钥保护、支持生物认证集成,以及提供跨平台统一 API 接口。这些特性使其成为处理敏感数据的首选方案。
2.2 加密机制与安全级别
flutter_secure_storage 采用 AES-256-GCM 作为核心加密算法,这是目前业界公认的强加密标准之一。AES-256 提供 256 位密钥长度,能够有效抵御暴力破解攻击;GCM 模式则提供了认证加密功能,不仅能加密数据,还能验证数据完整性,防止篡改。
在密钥管理层面,该库不要求开发者手动管理加密密钥,而是通过平台原生机制自动生成和保护密钥。在 Android 和 OpenHarmony 平台上,密钥由 Keystore/安全区生成并存储在硬件安全模块中,应用无法直接访问密钥内容,只能通过加密/解密接口间接使用。这种设计避免了密钥泄露的风险,即使攻击者获取了设备的 root 权限,也无法提取用于解密数据的密钥。
2.3 OpenHarmony 平台兼容要点
flutter_secure_storage 在 OpenHarmony 平台上的适配需要关注以下几个技术要点。
首先是安全区访问兼容性。OH 安全区通过 NAPI(Native API)接口暴露给上层应用,flutter_secure_storage 通过 Platform Channel 调用 NAPI 接口实现安全存储功能。在实际适配过程中,需要验证 NAPI 接口在目标 OH 版本上的可用性,并处理可能的平台差异。
其次是 TEE/REE 分级保护机制。不同价位的 OH 设备可能具有不同的安全能力:旗舰设备通常具备完整的 TEE 支持,可以实现硬件级的密钥保护;而部分入门级设备可能仅支持 REE 模式,需要应用进行兼容性检测并给出适当提示。
第三是性能与安全的平衡。安全区操作涉及 TEE 与 REE 之间的安全通信,相较于纯软件加密会有一定性能开销。在实际开发中,应该避免在主线程进行大量安全存储操作,建议采用异步方式处理。
三、方案对比:flutter_secure_storage 与 hive + encryption
3.1 技术架构差异
在 Flutter 生态中,除了 flutter_secure_storage 之外,hive + encryption 方案也是不少开发者的选择。两者在技术架构上存在显著差异,适用于不同的应用场景。
hive 是纯 Dart 实现的高性能键值数据库,通过 hive_ce(社区维护版本)提供了 OpenHarmony 平台支持。hive + encryption 方案的核心是 HiveAesCipher,使用 AES-256 算法对数据进行加密。然而,这两种方案在密钥管理上存在本质区别:hive 的加密密钥需要由应用自行生成和管理,通常存储在 SharedPreferences 或其他位置,这意味着密钥本身的安全性依赖于其他存储机制的保护;而 flutter_secure_storage 的密钥由系统级 KeyStore/安全区管理,提供了硬件级的保护。
3.2 功能特性对比
从数据存储方式来看,flutter_secure_storage 仅支持键值对存储,所有数据都以字符串形式存储和读取,对于复杂对象需要自行序列化(如 JSON 编码)。hive 则原生支持 Dart 类型系统,可以直接存储 Map、List、自定义对象等结构化数据,配合 hive_flutter 使用体验更加便捷。
在性能表现上,由于 hive 是纯 Dart 实现,不涉及 Platform Channel 调用,在纯软件加密场景下性能略优。但当涉及大量数据操作时,hive 的优势并不明显,而且安全存储的性能瓶颈主要在加密计算而非数据读写。
3.3 选型建议
基于以上分析,我们提出以下选型建议。
对于敏感凭证类数据(API Token、用户密码、OAuth 凭证、加密密钥等),强烈建议使用 flutter_secure_storage。这类数据对安全性要求极高,即使设备被攻破也需要确保无法被解密,而 flutter_secure_storage 的硬件级密钥保护能够提供最强保障。
对于业务结构化数据(用户资料、配置信息、缓存数据等),可以考虑使用 hive 作为主存储。如果对数据安全性有额外要求,可以在 hive 之上叠加加密,但需要注意密钥管理问题。或者考虑使用 SQLite with SQLCipher 等更成熟的加密数据库方案。
对于非敏感配置数据(UI 状态、主题偏好、语言设置等),可以直接使用 shared_preferences,无需额外的加密处理。
四、完整实现:从依赖配置到服务封装
4.1 项目配置
在 pubspec.yaml 中添加 flutter_secure_storage 依赖:
yaml
dependencies:
flutter:
sdk: flutter
flutter_secure_storage: ^9.2.4
get_it: ^8.3.0
flutter_riverpod: ^2.6.1
执行 flutter pub get 完成依赖安装。
4.2 服务封装设计
为了在项目中统一管理安全存储功能,并便于后续扩展和维护,建议封装一个独立的服务类。该服务类应包含以下设计要点:统一的初始化管理,避免重复创建存储实例;键名常量定义,防止硬编码字符串导致的潜在错误;便捷的业务方法封装,如 setApiToken、getApiToken 等,避免调用方直接操作底层接口;完整的异常处理,确保存储失败时不会导致应用崩溃;以及 OH 平台兼容性检测能力,让应用能够感知运行环境的安全能力。
以下是 SecureStorageService 的完整实现:
dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// OpenHarmony 安全存储服务
/// 基于 flutter_secure_storage 的敏感数据安全存储封装
///
/// OH 平台适配要点:
/// 1. Android 使用 Keystore,密钥受硬件安全模块保护
/// 2. iOS 使用 Keychain,支持生物认证保护
/// 3. OH 使用安全区 (Secure Area),支持 TEE/REE 分级保护
/// 4. 数据加密存储,即使设备被 root/越狱也能保持一定安全性
class SecureStorageService {
// 存储键常量定义
static const String keyApiToken = 'secure_api_token';
static const String keyRefreshToken = 'secure_refresh_token';
static const String keyUserName = 'secure_user_name';
static const String keyPassword = 'secure_password';
static const String keyEncryptionKey = 'secure_encryption_key';
static const String keySessionId = 'secure_session_id';
static const String keyClientId = 'secure_client_id';
static const String keyClientSecret = 'secure_client_secret';
FlutterSecureStorage? _storage;
bool _isInitialized = false;
// 默认配置(适用于大多数场景)
static const AndroidOptions _defaultAndroidOptions =
AndroidOptions(
encryptedSharedPreferences: true,
);
static const IOSOptions _defaultIOSOptions = IOSOptions(
accessibility: KeychainAccessibility.first_unlock_this_device,
);
SecureStorageService();
/// 初始化存储服务
Future<void> init() async {
if (_isInitialized) return;
_storage = FlutterSecureStorage(
aOptions: _defaultAndroidOptions,
iOptions: _defaultIOSOptions,
);
_isInitialized = true;
}
Future<FlutterSecureStorage> _ensureInitialized() async {
if (!_isInitialized || _storage == null) {
await init();
}
return _storage!;
}
// 基础 CRUD 操作
Future<void> write(String key, String value) async {
final storage = await _ensureInitialized();
await storage.write(key: key, value: value);
}
Future<String?> read(String key) async {
final storage = await _ensureInitialized();
return await storage.read(key: key);
}
Future<bool> containsKey(String key) async {
final storage = await _ensureInitialized();
return await storage.containsKey(key: key);
}
Future<void> delete(String key) async {
final storage = await _ensureInitialized();
await storage.delete(key: key);
}
Future<void> deleteAll() async {
final storage = await _ensureInitialized();
await storage.deleteAll();
}
Future<Map<String, String>> readAll() async {
final storage = await _ensureInitialized();
return await storage.readAll();
}
// API Token 操作
Future<void> setApiToken(String token) async {
await write(keyApiToken, token);
}
Future<String?> getApiToken() async {
return await read(keyApiToken);
}
Future<bool> hasApiToken() async {
final token = await getApiToken();
return token != null && token.isNotEmpty;
}
Future<void> clearApiToken() async {
await delete(keyApiToken);
}
// 刷新令牌操作
Future<void> setRefreshToken(String token) async {
await write(keyRefreshToken, token);
}
Future<String?> getRefreshToken() async {
return await read(keyRefreshToken);
}
Future<void> clearRefreshToken() async {
await delete(keyRefreshToken);
}
// 用户凭证操作
Future<void> setUserCredentials({
required String userName,
required String password,
}) async {
await Future.wait([
write(keyUserName, userName),
write(keyPassword, password),
]);
}
Future<String?> getUserName() async {
return await read(keyUserName);
}
Future<String?> getPassword() async {
return await read(keyPassword);
}
Future<void> clearUserCredentials() async {
await Future.wait([
delete(keyUserName),
delete(keyPassword),
]);
}
Future<bool> hasSavedCredentials() async {
final userName = await getUserName();
final password = await getPassword();
return (userName != null && userName.isNotEmpty) &&
(password != null && password.isNotEmpty);
}
// 加密密钥操作
Future<void> setEncryptionKey(String key) async {
await write(keyEncryptionKey, key);
}
Future<String?> getEncryptionKey() async {
return await read(keyEncryptionKey);
}
Future<void> clearEncryptionKey() async {
await delete(keyEncryptionKey);
}
Future<String> generateAndStoreEncryptionKey({
int length = 32,
}) async {
final random = DateTime.now().millisecondsSinceEpoch.toString();
final key = '${random}_${length}_secure_key';
await setEncryptionKey(key);
return key;
}
// 会话管理操作
Future<void> setSessionId(String sessionId) async {
await write(keySessionId, sessionId);
}
Future<String?> getSessionId() async {
return await read(keySessionId);
}
Future<void> clearSession() async {
await Future.wait([
clearApiToken(),
clearRefreshToken(),
clearSessionId(),
]);
}
Future<void> clearSessionId() async {
await delete(keySessionId);
}
Future<bool> isSessionValid() async {
final token = await getApiToken();
final sessionId = await getSessionId();
return (token != null && token.isNotEmpty) ||
(sessionId != null && sessionId.isNotEmpty);
}
// OAuth 配置操作
Future<void> setOAuthConfig({
required String clientId,
required String clientSecret,
}) async {
await Future.wait([
write(keyClientId, clientId),
write(keyClientSecret, clientSecret),
]);
}
Future<String?> getClientId() async {
return await read(keyClientId);
}
Future<String?> getClientSecret() async {
return await read(keyClientSecret);
}
Future<void> clearOAuthConfig() async {
await Future.wait([
delete(keyClientId),
delete(keyClientSecret),
]);
}
// 批量操作
Future<void> clearAllAuthData() async {
await Future.wait([
clearApiToken(),
clearRefreshToken(),
clearSessionId(),
clearUserCredentials(),
clearEncryptionKey(),
]);
}
Future<void> clearAllSecureData() async {
await deleteAll();
}
// OH 平台特定方法
Future<String> getSecurityLevel() async {
try {
await write('_test_key', 'test_value');
await delete('_test_key');
return 'TEE_Secure';
} catch (e) {
return 'REE_Normal';
}
}
Future<bool> isHardwareBacked() async {
try {
final level = await getSecurityLevel();
return level == 'TEE_Secure';
} catch (e) {
return false;
}
}
/// OH 平台兼容性检测
Future<Map<String, dynamic>> checkOHCompatibility() async {
final result = <String, dynamic>{
'isCompatible': false,
'securityLevel': 'unknown',
'supportsHardwareEncryption': false,
'supportsSecureArea': false,
'error': null,
};
try {
// 1. 测试基础读写功能
await write('_compat_test', 'test_value');
final readValue = await read('_compat_test');
if (readValue != 'test_value') {
result['error'] = 'Read/write test failed';
return result;
}
await delete('_compat_test');
// 2. 检查安全级别
result['securityLevel'] = await getSecurityLevel();
result['supportsHardwareEncryption'] = await isHardwareBacked();
// 3. OH 安全区支持检测
result['supportsSecureArea'] =
result['securityLevel'] == 'TEE_Secure';
// 4. 综合判断兼容性
result['isCompatible'] = true;
return result;
} catch (e) {
result['error'] = e.toString();
return result;
}
}
}
4.3 依赖注入配置
使用 get_it 实现依赖注入,在 injection.dart 中注册服务:
dart
import 'package:get_it/get_it.dart';
import 'services/secure_storage_service.dart';
final getIt = GetIt.instance;
Future<void> configureDependencies() async {
// 注册 SecureStorageService(敏感数据安全存储)
// OH 平台使用安全区 (Secure Area) 保护加密密钥
// 支持 KeyChain (iOS) / Keystore (Android) / OH 安全区
getIt.registerLazySingleton<SecureStorageService>(
() => SecureStorageService(),
);
}
在应用入口初始化:
dart
import 'package:flutter/material.dart';
import 'injection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await configureDependencies();
runApp(const ProviderScope(child: MyApp()));
}
4.4 路由配置
在 router.dart 中添加安全存储演示页面的路由:
dart
class AppRoutes {
// ... 其他路由 ...
static const String secureStorageDemo = '/secure-storage-demo';
}
// 在 _buildRoutes() 中添加路由
GoRoute(
path: AppRoutes.secureStorageDemo,
name: 'secureStorageDemo',
pageBuilder: (context, state) => OHRouteTransitionPage(
key: state.pageKey,
child: const SecureStorageDemoPage(),
),
),
五、演示页面实现
为了便于测试和演示安全存储功能,我们实现了一个功能完善的演示页面,包含 OH 安全区兼容性检测、Token 存储操作、用户凭证存储操作、快速操作按钮以及方案对比说明等功能模块。
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../injection.dart';
import '../services/secure_storage_service.dart';
import '../utils/flutter_animate_utils.dart';
class SecureStorageDemoPage extends ConsumerStatefulWidget {
const SecureStorageDemoPage({super.key});
@override
ConsumerState<SecureStorageDemoPage> createState() =>
_SecureStorageDemoPageState();
}
class _SecureStorageDemoPageState
extends ConsumerState<SecureStorageDemoPage> {
late final SecureStorageService _secureStorage;
bool _isLoading = false;
String _logMessage = '';
Map<String, dynamic>? _compatResult;
bool _hasApiToken = false;
bool _hasCredentials = false;
final _apiTokenController = TextEditingController();
final _userNameController = TextEditingController();
final _passwordController = TextEditingController();
@override
void initState() {
super.initState();
_secureStorage = getIt<SecureStorageService>();
_loadCurrentState();
}
@override
void dispose() {
_apiTokenController.dispose();
_userNameController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _loadCurrentState() async {
setState(() => _isLoading = true);
try {
final hasToken = await _secureStorage.hasApiToken();
final hasCreds = await _secureStorage.hasSavedCredentials();
if (mounted) {
setState(() {
_hasApiToken = hasToken;
_hasCredentials = hasCreds;
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_logMessage = '加载状态失败: $e';
});
}
}
}
void _addLog(String message) {
setState(() {
_logMessage = '${DateTime.now().toString().substring(11, 19)} $message';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('安全存储演示'),
actions: [
IconButton(
icon: const Icon(Icons.security),
onPressed: _checkOHCompatibility,
tooltip: '检查 OH 兼容性',
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCompatibilityCard(),
const SizedBox(height: 16),
_buildCurrentStatusCard(),
const SizedBox(height: 16),
_buildApiTokenSection(),
const SizedBox(height: 16),
_buildCredentialsSection(),
const SizedBox(height: 16),
_buildQuickActionsSection(),
const SizedBox(height: 16),
_buildComparisonSection(),
const SizedBox(height: 16),
_buildLogSection(),
const SizedBox(height: 32),
],
),
),
);
}
Widget _buildCompatibilityCard() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'OH 安全区兼容性检测',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
if (_compatResult != null) ...[
_buildCompatItem(
'兼容性状态',
_compatResult!['isCompatible'] == true ? '✓ 兼容' : '✗ 不兼容',
_compatResult!['isCompatible'] == true
? Colors.green
: Colors.red,
),
_buildCompatItem(
'安全级别',
_compatResult!['securityLevel'] ?? '未知',
_compatResult!['securityLevel'] == 'TEE_Secure'
? Colors.green
: Colors.orange,
),
_buildCompatItem(
'硬件加密支持',
_compatResult!['supportsHardwareEncryption'] == true
? '✓ 支持'
: '✗ 不支持',
_compatResult!['supportsHardwareEncryption'] == true
? Colors.green
: Colors.orange,
),
_buildCompatItem(
'安全区 (Secure Area)',
_compatResult!['supportsSecureArea'] == true
? '✓ 支持'
: '✗ 不支持',
_compatResult!['supportsSecureArea'] == true
? Colors.green
: Colors.orange,
),
] else ...[
const Text(
'点击右上角安全图标检测 OpenHarmony 安全区兼容性',
style: TextStyle(color: Colors.grey),
),
],
],
),
),
);
}
Widget _buildCompatItem(String label, String value, Color statusColor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withAlpha(25),
borderRadius: BorderRadius.circular(12),
),
child: Text(
value,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
Widget _buildCurrentStatusCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'当前存储状态',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatusChip(
'API Token',
_hasApiToken,
Icons.vpn_key,
Colors.blue,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatusChip(
'用户凭证',
_hasCredentials,
Icons.person,
Colors.purple,
),
),
],
),
],
),
),
);
}
Widget _buildStatusChip(
String label, bool hasData, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: hasData ? color.withAlpha(25) : Colors.grey.withAlpha(25),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(icon, color: hasData ? color : Colors.grey),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontSize: 12)),
Text(
hasData ? '已存储' : '未存储',
style: TextStyle(
fontWeight: FontWeight.w600,
color: hasData ? color : Colors.grey,
),
),
],
),
],
),
);
}
Widget _buildApiTokenSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'API Token 安全存储',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
TextField(
controller: _apiTokenController,
decoration: InputDecoration(
labelText: 'API Token',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
obscureText: true,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _saveApiToken,
icon: const Icon(Icons.save),
label: const Text('保存 Token'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: _getApiToken,
icon: const Icon(Icons.visibility),
label: const Text('读取 Token'),
),
),
IconButton(
onPressed: _clearApiToken,
icon: const Icon(Icons.delete_outline, color: Colors.red),
),
],
),
],
),
),
);
}
Widget _buildCredentialsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'用户凭证安全存储',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
TextField(
controller: _userNameController,
decoration: InputDecoration(
labelText: '用户名',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
const SizedBox(height: 12),
TextField(
controller: _passwordController,
decoration: InputDecoration(
labelText: '密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
obscureText: true,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _saveCredentials,
icon: const Icon(Icons.save),
label: const Text('保存凭证'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: _getCredentials,
icon: const Icon(Icons.visibility),
label: const Text('读取凭证'),
),
),
IconButton(
onPressed: _clearCredentials,
icon: const Icon(Icons.delete_outline, color: Colors.red),
),
],
),
],
),
),
);
}
Widget _buildQuickActionsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'快捷操作',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildActionChip(
'生成加密密钥',
Icons.key,
Colors.orange,
_generateEncryptionKey,
),
_buildActionChip(
'模拟登录会话',
Icons.login,
Colors.green,
_simulateLogin,
),
_buildActionChip(
'退出登录',
Icons.logout,
Colors.red,
_logout,
),
_buildActionChip(
'清除全部',
Icons.delete_forever,
Colors.grey,
_clearAllData,
),
],
),
],
),
),
);
}
Widget _buildActionChip(
String label,
IconData icon,
Color color,
VoidCallback onTap,
) {
return ActionChip(
avatar: Icon(icon, size: 18, color: color),
label: Text(label),
onPressed: onTap,
backgroundColor: color.withAlpha(25),
);
}
Widget _buildComparisonSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'方案对比',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 12),
Table(
columnWidths: const {
0: FlexColumnWidth(1.2),
1: FlexColumnWidth(1),
2: FlexColumnWidth(1),
},
border: TableBorder.all(color: Colors.grey[300]!),
children: [
const TableRow(
decoration: BoxDecoration(color: Color(0xFFF5F5F5)),
children: [
Padding(
padding: EdgeInsets.all(8),
child: Text('特性', style: TextStyle(fontWeight: FontWeight.bold)),
),
Padding(
padding: EdgeInsets.all(8),
child: Text('flutter_secure_storage', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center),
),
Padding(
padding: EdgeInsets.all(8),
child: Text('hive + encryption', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center),
),
],
),
_buildTableRow('加密方式', 'AES-256-GCM', 'AES-256'),
_buildTableRow('OH 兼容性', '✓ 良好', '⚠️ 需测试'),
_buildTableRow('结构化数据', '⚠️ 仅键值', '✓ 原生支持'),
_buildTableRow('性能', '中等', '✓ 高性能'),
_buildTableRow('适用场景', 'Token/凭证', '大量业务数据'),
],
),
],
),
),
);
}
TableRow _buildTableRow(String feature, String secureStorage, String hive) {
return TableRow(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Text(feature, style: const TextStyle(fontSize: 12)),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(secureStorage,
style: const TextStyle(fontSize: 12), textAlign: TextAlign.center),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(hive,
style: const TextStyle(fontSize: 12), textAlign: TextAlign.center),
),
],
);
}
Widget _buildLogSection() {
return Card(
color: Colors.grey[900],
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'操作日志',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Container(
height: 120,
width: double.infinity,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(8),
),
child: SingleChildScrollView(
child: Text(
_logMessage.isEmpty ? '等待操作...' : _logMessage,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: _logMessage.isEmpty ? Colors.grey : Colors.green[300],
),
),
),
),
],
),
),
);
}
// 操作方法
Future<void> _checkOHCompatibility() async {
setState(() => _isLoading = true);
_addLog('正在检测 OH 安全区兼容性...');
try {
final result = await _secureStorage.checkOHCompatibility();
setState(() {
_compatResult = result;
_isLoading = false;
});
if (result['isCompatible'] == true) {
_addLog('✓ OH 安全区兼容性检测通过');
_addLog(' 安全级别: ${result['securityLevel']}');
} else {
_addLog('✗ OH 安全区兼容性检测失败');
}
} catch (e) {
setState(() => _isLoading = false);
_addLog('✗ 检测异常: $e');
}
}
Future<void> _saveApiToken() async {
final token = _apiTokenController.text.trim();
if (token.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入 Token')));
return;
}
_addLog('正在保存 API Token...');
await _secureStorage.setApiToken(token);
_addLog('✓ API Token 已加密存储');
_apiTokenController.clear();
await _loadCurrentState();
}
Future<void> _getApiToken() async {
_addLog('正在读取 API Token...');
final token = await _secureStorage.getApiToken();
if (token != null && token.isNotEmpty) {
_addLog('✓ Token 读取成功 (长度: ${token.length})');
_showTokenDialog(token);
} else {
_addLog('✗ 未找到存储的 Token');
}
}
void _showTokenDialog(String token) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('API Token'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('已存储的 Token:'),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(
token,
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
],
),
);
}
Future<void> _clearApiToken() async {
await _secureStorage.clearApiToken();
_addLog('✓ API Token 已清除');
await _loadCurrentState();
}
Future<void> _saveCredentials() async {
final userName = _userNameController.text.trim();
final password = _passwordController.text.trim();
if (userName.isEmpty || password.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写用户名和密码')));
return;
}
_addLog('正在保存用户凭证...');
await _secureStorage.setUserCredentials(
userName: userName,
password: password,
);
_addLog('✓ 用户凭证已加密存储');
_userNameController.clear();
_passwordController.clear();
await _loadCurrentState();
}
Future<void> _getCredentials() async {
_addLog('正在读取用户凭证...');
final userName = await _secureStorage.getUserName();
final password = await _secureStorage.getPassword();
if (userName != null && password != null) {
_addLog('✓ 凭证读取成功');
} else {
_addLog('✗ 未找到存储的凭证');
}
}
Future<void> _clearCredentials() async {
await _secureStorage.clearUserCredentials();
_addLog('✓ 用户凭证已清除');
await _loadCurrentState();
}
Future<void> _generateEncryptionKey() async {
_addLog('正在生成加密密钥...');
await _secureStorage.generateAndStoreEncryptionKey();
_addLog('✓ 加密密钥已生成并存储');
}
Future<void> _simulateLogin() async {
_addLog('模拟登录流程...');
await _secureStorage.setApiToken('mock_token_${DateTime.now().millisecondsSinceEpoch}');
await _secureStorage.setRefreshToken('mock_refresh_${DateTime.now().millisecondsSinceEpoch}');
await _secureStorage.setSessionId('session_${DateTime.now().millisecondsSinceEpoch}');
_addLog('✓ 登录会话已创建');
await _loadCurrentState();
}
Future<void> _logout() async {
_addLog('正在退出登录...');
await _secureStorage.clearSession();
_addLog('✓ 会话已清除');
await _loadCurrentState();
}
Future<void> _clearAllData() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认清除'),
content: const Text('确定要清除所有安全存储数据吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('确认清除'),
),
],
),
);
if (confirmed == true) {
_addLog('正在清除所有数据...');
await _secureStorage.clearAllSecureData();
_addLog('✓ 所有数据已清除');
await _loadCurrentState();
}
}
}
六、实际应用场景与最佳实践
6.1 登录认证流程集成
在真实的移动应用中,安全存储最典型的应用场景是登录认证流程。以下是一个完整的集成示例,展示了如何将 SecureStorageService 融入登录认证体系。
应用启动时,首先检查是否存在有效的会话信息。如果存在有效 Token,直接进入应用首页;如果 Token 失效但存在 Refresh Token,尝试自动刷新;如果两者都不存在,则展示登录页面。
dart
class AuthRepository {
final SecureStorageService _secureStorage;
final Dio _dioClient;
AuthRepository({
required SecureStorageService secureStorage,
required Dio dioClient,
}) : _secureStorage = secureStorage,
_dioClient = dioClient;
/// 检查认证状态
Future<AuthState> checkAuthState() async {
final hasToken = await _secureStorage.hasApiToken();
if (!hasToken) {
return AuthState.unauthenticated;
}
final token = await _secureStorage.getApiToken();
final isValid = await _verifyToken(token!);
if (isValid) {
return AuthState.authenticated;
}
// Token 失效,尝试刷新
final hasRefreshToken = await _secureStorage.getRefreshToken();
if (hasRefreshToken != null) {
final refreshed = await _refreshAccessToken(hasRefreshToken);
if (refreshed) {
return AuthState.authenticated;
}
}
// 刷新失败,清除会话
await _secureStorage.clearSession();
return AuthState.unauthenticated;
}
/// 登录
Future<LoginResult> login(String username, String password) async {
try {
final response = await _dioClient.post('/auth/login', data: {
'username': username,
'password': password,
});
final accessToken = response.data['access_token'] as String;
final refreshToken = response.data['refresh_token'] as String;
await _secureStorage.setApiToken(accessToken);
await _secureStorage.setRefreshToken(refreshToken);
return LoginResult.success();
} on DioException catch (e) {
return LoginResult.failure(e.message ?? '登录失败');
}
}
/// 登出
Future<void> logout() async {
await _secureStorage.clearSession();
}
Future<bool> _verifyToken(String token) async {
// 实现 Token 验证逻辑
return true;
}
Future<bool> _refreshAccessToken(String refreshToken) async {
// 实现 Token 刷新逻辑
return true;
}
}
enum AuthState { authenticated, unauthenticated, loading }
6.2 安全存储使用注意事项
在实际使用安全存储功能时,开发者应当注意以下几个关键点。
首先是数据分类原则。并非所有数据都需要使用安全存储,过度使用不仅浪费系统资源,还可能影响性能。建议仅对真正敏感的凭证类数据使用安全存储,如密码、Token、密钥等;对于一般配置和缓存数据,使用常规存储即可。
其次是异常处理机制。安全存储操作可能因多种原因失败,包括但不限于设备不支持安全区、存储空间不足、权限问题等。应用应当捕获并妥善处理这些异常,避免因存储失败导致应用崩溃,同时向用户给出友好的错误提示。
第三是数据清理策略。在用户登出或账户注销时,务必清除所有敏感数据。清除操作应当确保数据真正从存储介质中删除,而非仅仅标记为删除。对于高敏感场景,可以考虑多次覆写以确保数据不可恢复。
第四是性能优化考量。安全存储操作涉及加密计算和可能的 TEE 通信,相较于普通存储有更大的性能开销。在 UI 线程中执行大量安全存储操作可能导致界面卡顿。建议将安全存储操作放在后台异步执行,并通过状态管理机制更新 UI。
这是我的运行截图:
七、总结与展望
本文系统性地探讨了 flutter_secure_storage 在 OpenHarmony 平台上的适配实践。通过对 OH 安全区架构的深入分析,我们理解了该库在鸿蒙设备上实现硬件级安全保护的底层原理。通过实际代码示例,展示了从依赖配置、服务封装到功能集成的完整实现路径。同时,通过与 hive + encryption 方案的对比,帮助开发者在不同场景下做出合理的技术选型。
flutter_secure_storage 为 Flutter 应用提供了可靠的安全存储能力,在 OpenHarmony 平台上能够有效利用 OH 安全区实现敏感数据的硬件级保护。对于需要处理用户凭证、API Token 等敏感数据的应用而言,该库是值得信赖的选择。
值得指出的是,安全存储只是应用安全体系的一个环节。真正完善的应用安全还需要考虑数据传输安全(如 HTTPS)、代码混淆与加固、运行时环境检测等多个维度。建议开发者在使用安全存储的同时,综合考虑其他安全措施,构建多层次的防护体系。