Flutter安全存储插件flutter_secure_storage的使用
Flutter安全存储插件flutter_secure_storage的使用
简介
flutter_secure_storage
是一个用于在Flutter应用程序中安全存储数据的插件。它使用平台特定的安全机制来确保数据的安全性:
- iOS: 使用Keychain进行存储。
- Android: 使用AES加密,AES密钥用RSA加密并存储在KeyStore中。从V5.0.0开始,可以通过启用
EncryptedSharedPreferences
来增强安全性。 - Linux: 使用
libsecret
进行存储。 - Web: 实验性实现,使用WebCrypto API,需要注意HTTP Strict Transport Security配置。
- Windows: 当前实现不支持
readAll
和deleteAll
方法。 - macOS: 需要添加Keychain Sharing能力。
注意事项
Android上的encryptedSharedPreferences
参数使用
当在Android上使用encryptedSharedPreferences
参数时,请确保将其传递给构造函数而不是函数,以避免混合使用导致的错误:
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
final storage = FlutterSecureStorage(aOptions: _getAndroidOptions());
更多详情可以参考这个问题。
Android版本配置
确保在build.gradle
文件中设置minSdkVersion
为>=18,并且需要禁用自动备份或排除FlutterSecureStorage
使用的共享偏好设置文件,以防止java.security.InvalidKeyException
异常。
Web版本配置
由于Web端是实验性实现,需确保启用了HTTP Strict Forward Secrecy,并正确设置了响应头,以防止JavaScript劫持。
Linux版本配置
构建项目时需要安装libsecret-1-dev
和libjsoncpp-dev
,运行应用时则需要libsecret-1-0
和libjsoncpp1
作为依赖项。
Windows和macOS版本配置
- Windows: 不支持
readAll
和deleteAll
方法。 - macOS: 需要在
DebugProfile.entitlements
和Release.entitlements
文件中添加Keychain Sharing权限。
使用示例
下面是一个完整的Flutter应用示例,展示了如何使用flutter_secure_storage
插件来进行安全的数据读取、写入、删除等操作。
import 'dart:io' show Platform;
import 'dart:math' show Random;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
void main() {
runApp(const MaterialApp(home: ItemsWidget()));
}
enum _Actions { deleteAll, isProtectedDataAvailable }
enum _ItemActions { delete, edit, containsKey, read }
class ItemsWidget extends StatefulWidget {
const ItemsWidget({super.key});
@override
ItemsWidgetState createState() => ItemsWidgetState();
}
class ItemsWidgetState extends State<ItemsWidget> {
final FlutterSecureStorage _storage = const FlutterSecureStorage();
final TextEditingController _accountNameController =
TextEditingController(text: 'flutter_secure_storage_service');
final List<_SecItem> _items = [];
@override
void initState() {
super.initState();
_accountNameController.addListener(_readAll);
_readAll();
}
@override
void dispose() {
_accountNameController.removeListener(_readAll);
_accountNameController.dispose();
super.dispose();
}
Future<void> _readAll() async {
final Map<String, String> all = await _storage.readAll(
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
setState(() {
_items
..clear()
..addAll(all.entries.map((e) => _SecItem(e.key, e.value)))
..sort((a, b) => int.parse(a.key).compareTo(int.parse(b.key)));
});
}
Future<void> _deleteAll() async {
await _storage.deleteAll(
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
_readAll();
}
Future<void> _isProtectedDataAvailable() async {
final ScaffoldMessengerState scaffold = ScaffoldMessenger.of(context);
final bool? result = await _storage.isCupertinoProtectedDataAvailable();
scaffold.showSnackBar(
SnackBar(
content: Text('Protected data available: $result'),
backgroundColor: result != null && result ? Colors.green : Colors.red,
),
);
}
Future<void> _addNewItem() async {
await _storage.write(
key: DateTime.timestamp().microsecondsSinceEpoch.toString(),
value: _randomValue(),
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
_readAll();
}
IOSOptions _getIOSOptions() => IOSOptions(
accountName: _getAccountName(),
);
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
// sharedPreferencesName: 'Test2',
// preferencesKeyPrefix: 'Test'
);
String? _getAccountName() =>
_accountNameController.text.isEmpty ? null : _accountNameController.text;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
actions: <Widget>[
IconButton(
key: const Key('add_random'),
onPressed: _addNewItem,
icon: const Icon(Icons.add),
),
PopupMenuButton<_Actions>(
key: const Key('popup_menu'),
onSelected: (action) {
switch (action) {
case _Actions.deleteAll:
_deleteAll();
case _Actions.isProtectedDataAvailable:
_isProtectedDataAvailable();
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<_Actions>>[
const PopupMenuItem(
key: Key('delete_all'),
value: _Actions.deleteAll,
child: Text('Delete all'),
),
const PopupMenuItem(
key: Key('is_protected_data_available'),
value: _Actions.isProtectedDataAvailable,
child: Text('IsProtectedDataAvailable'),
),
],
),
],
),
body: Column(
children: [
if (!kIsWeb && Platform.isIOS)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextFormField(
controller: _accountNameController,
decoration: const InputDecoration(labelText: 'kSecAttrService'),
),
),
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) => ListTile(
trailing: PopupMenuButton(
key: Key('popup_row_$index'),
onSelected: (_ItemActions action) =>
_performAction(action, _items[index], context),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<_ItemActions>>[
PopupMenuItem(
value: _ItemActions.delete,
child: Text(
'Delete',
key: Key('delete_row_$index'),
),
),
PopupMenuItem(
value: _ItemActions.edit,
child: Text(
'Edit',
key: Key('edit_row_$index'),
),
),
PopupMenuItem(
value: _ItemActions.containsKey,
child: Text(
'Contains Key',
key: Key('contains_row_$index'),
),
),
PopupMenuItem(
value: _ItemActions.read,
child: Text(
'Read',
key: Key('contains_row_$index'),
),
),
],
),
title: Text(
_items[index].value,
key: Key('title_row_$index'),
),
subtitle: Text(
_items[index].key,
key: Key('subtitle_row_$index'),
),
),
),
),
],
),
);
}
Future<void> _performAction(
_ItemActions action,
_SecItem item,
BuildContext context,
) async {
switch (action) {
case _ItemActions.delete:
await _storage.delete(
key: item.key,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
_readAll();
case _ItemActions.edit:
if (!context.mounted) return;
final String? result = await showDialog<String>(
context: context,
builder: (_) => _EditItemWidget(item.value),
);
if (result != null) {
await _storage.write(
key: item.key,
value: result,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
_readAll();
}
case _ItemActions.containsKey:
final String key = await _displayTextInputDialog(context, item.key);
final bool result = await _storage.containsKey(key: key);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Contains Key: $result, key checked: $key'),
backgroundColor: result ? Colors.green : Colors.red,
),
);
case _ItemActions.read:
final key = await _displayTextInputDialog(context, item.key);
final result =
await _storage.read(key: key, aOptions: _getAndroidOptions());
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('value: $result'),
),
);
}
}
Future<String> _displayTextInputDialog(
BuildContext context,
String key,
) async {
final TextEditingController controller = TextEditingController(text: key);
await showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Check if key exists'),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: const Text('OK'),
),
],
content: TextField(controller: controller),
),
);
return controller.text;
}
String _randomValue() {
final Random rand = Random();
final List<int> codeUnits =
List.generate(20, (_) => rand.nextInt(26) + 65, growable: false);
return String.fromCharCodes(codeUnits);
}
}
class _EditItemWidget extends StatelessWidget {
_EditItemWidget(String text)
: _controller = TextEditingController(text: text);
final TextEditingController _controller;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Edit item'),
content: TextField(
key: const Key('title_field'),
controller: _controller,
autofocus: true,
),
actions: <Widget>[
TextButton(
key: const Key('cancel'),
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
TextButton(
key: const Key('save'),
onPressed: () => Navigator.of(context).pop(_controller.text),
child: const Text('Save'),
),
],
);
}
}
class _SecItem {
const _SecItem(this.key, this.value);
final String key;
final String value;
}
此代码创建了一个简单的Flutter应用程序,允许用户添加、编辑、删除和查看安全存储中的项目。通过使用flutter_secure_storage
插件,所有数据都被安全地存储在设备的安全存储区域中。
更多关于Flutter安全存储插件flutter_secure_storage的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter安全存储插件flutter_secure_storage的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何使用Flutter安全存储插件flutter_secure_storage
的代码示例。这个插件允许你在Flutter应用中安全地存储和检索敏感数据,比如用户凭证等。
首先,你需要在你的pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
flutter_secure_storage: ^5.0.2 # 请检查最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,你可以在你的Flutter应用中这样使用flutter_secure_storage
:
- 导入插件
在你的Dart文件中导入插件:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
- 初始化存储
你可以在你的应用启动时初始化FlutterSecureStorage
实例:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final storage = new FlutterSecureStorage();
// 你可以在这里执行一些初始化操作,比如检查存储中是否有必要的数据
// await storage.read(key: 'some_key');
runApp(MyApp());
}
- 存储数据
你可以使用write
方法来存储数据:
Future<void> saveUserData(String key, String value) async {
final storage = new FlutterSecureStorage();
await storage.write(key: key, value: value);
}
例如,存储用户令牌:
await saveUserData('user_token', 'your_user_token_here');
- 读取数据
你可以使用read
方法来读取数据:
Future<String?> readUserData(String key) async {
final storage = new FlutterSecureStorage();
return await storage.read(key: key);
}
例如,读取用户令牌:
final userToken = await readUserData('user_token');
print('User Token: $userToken');
- 删除数据
你可以使用delete
方法来删除数据:
Future<void> deleteUserData(String key) async {
final storage = new FlutterSecureStorage();
await storage.delete(key: key);
}
例如,删除用户令牌:
await deleteUserData('user_token');
- 完整示例
下面是一个完整的示例,展示了如何在Flutter应用中存储和读取用户令牌:
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SecureStorageExample(),
);
}
}
class SecureStorageExample extends StatefulWidget {
@override
_SecureStorageExampleState createState() => _SecureStorageExampleState();
}
class _SecureStorageExampleState extends State<SecureStorageExample> {
final FlutterSecureStorage storage = FlutterSecureStorage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Secure Storage Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () async {
await storage.write(key: 'user_token', value: 'your_user_token_here');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Token saved!')),
);
},
child: Text('Save Token'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: async () {
final token = await storage.read(key: 'user_token');
if (token != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("User Token: $token")),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No token found')),
);
}
},
child: Text('Read Token'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: async () {
await storage.delete(key: 'user_token');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Token deleted!')),
);
},
child: Text('Delete Token'),
),
],
),
),
);
}
}
这个示例展示了一个简单的Flutter应用,其中包含了三个按钮,分别用于存储、读取和删除用户令牌。希望这个示例能帮助你理解如何使用flutter_secure_storage
插件。