Flutter密钥存储管理插件keychain_access的使用
Flutter密钥存储管理插件keychain_access的使用
Flutter 插件用于访问 MacOS 和 iOS 上的 Keychain Access APIs。
功能
以下操作是可用的:
- 存储安全数据
- 检索安全数据
- 删除安全数据
介绍
为什么:
在我看来,对于 Android 平台,有许多稳定的库或插件可用。然而,可靠的 MacOS 和 iOS 插件似乎并不存在。
该插件目前仅支持 MacOS 和 iOS,并且可能根据需求扩展到其他平台。
它利用了 Keychain Services API 来添加安全数据、查询安全数据和删除安全数据。
使用
要将安全数据添加到钥匙串中:
import 'package:keychain_access/keychain_access.dart';
await keychainAccess.addSecureData(
"uniqueSecureDataIdentifier", # 示例:用户名,用户ID等。
"<secure-password>", # -> 您的安全数据在这里。
application: "com.company.exampleApp" # -> 这是可选的。
);
// *注意:如果键已存在,这将抛出 PlatformException。
// 使用 addOrUpdateSecureData 替换已有记录。
要更新钥匙串中的安全数据:
import 'package:keychain_access/keychain_access.dart';
await keychainAccess.updateSecureData(
"uniqueSecureDataIdentifier", # 示例:用户名,用户ID等。
"<secure-password>", # -> 您的安全数据在这里。
application: "com.company.exampleApp" # -> 这是可选的。
);
// *注意:如果键不存在,这将抛出 PlatformException。
// 使用 addOrUpdateSecureData 添加新记录。
// 但是,这不能保证线程安全性。
要向钥匙串添加或更新安全数据:
import 'package:keychain_access/keychain_access.dart';
await keychainAccess.addOrUpdateSecureData(
"uniqueSecureDataIdentifier", # 示例:用户名,用户ID等。
"<secure-password>", # -> 您的安全数据在这里。
application: "com.company.exampleApp" # -> 这是可选的。
);
要从钥匙串中删除安全数据:
import 'package:keychain_access/keychain_access.dart';
await keychainAccess.deleteSecureData(
"uniqueSecureDataIdentifier", # 示例:用户名,用户ID等。
"<secure-password>", # -> 您的安全数据在这里。
application: "com.company.exampleApp" # -> 这是可选的。
);
// *注意:如果键不存在,这将抛出 PlatformException。
完整示例
以下是一个完整的示例代码,展示了如何使用 keychain_access
插件来管理钥匙串中的安全数据。
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:keychain_access/keychain_access.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
[@override](/user/override)
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _simpleResults = _PluginFunctionResults();
final _resultsForApplicationName = _PluginFunctionResults();
final _keychainAccessPlugin = KeychainAccess();
final applicationName = 'org.gps.flutterKeychainAccessPlugin';
final exampleUsername = 'username123';
final examplePassword = 'password123';
final exampleUpdatedPassword = 'updatedPassword123';
final exampleAddOrUpdatePassword = 'addOrUpdatePassword123';
[@override](/user/override)
void initState() {
super.initState();
initPlatformState();
}
Future<void> _triggerAddPassword(_PluginFunctionResults results,
{String? application}) async {
String passwordAddSuccessful;
try {
passwordAddSuccessful = await _keychainAccessPlugin.addSecureData(
exampleUsername, examplePassword,
application: application)
? "Success"
: "FAILED";
} on PlatformException catch (e) {
passwordAddSuccessful = 'Failed to addPassword $e';
}
// 如果小部件在异步平台消息传输期间被从树中移除,则我们希望丢弃回复而不是调用 setState 更新我们的非存在的外观。
if (!mounted) return;
setState(() {
results.addPasswordStatus.message = passwordAddSuccessful;
results.addPasswordStatus.status = passwordAddSuccessful == "Success";
});
}
Future<void> _triggerUpdatePassword(_PluginFunctionResults results,
{String? application}) async {
String passwordUpdateSuccessful;
try {
passwordUpdateSuccessful = await _keychainAccessPlugin.updateSecureData(
exampleUsername, exampleUpdatedPassword,
application: application)
? "Success"
: "FAILED";
} on PlatformException catch (e) {
passwordUpdateSuccessful = 'Failed to updatePassword $e';
}
// 如果小部件在异步平台消息传输期间被从树中移除,则我们希望丢弃回复而不是调用 setState 更新我们的非存在的外观。
if (!mounted) return;
setState(() {
results.updatePasswordStatus.message = passwordUpdateSuccessful;
results.updatePasswordStatus.status =
passwordUpdateSuccessful == 'Success';
});
}
Future<void> _triggerAddOrUpdatePassword(_PluginFunctionResults results,
{String? application}) async {
String addOrUpdatePasswordSuccessful;
try {
addOrUpdatePasswordSuccessful = await _keychainAccessPlugin
.updateSecureData(exampleUsername, exampleAddOrUpdatePassword,
application: application)
? "Success"
: "FAILED";
} on PlatformException catch (e) {
addOrUpdatePasswordSuccessful = 'Failed to addOrUpdatePassword $e';
}
// 如果小部件在异步平台消息传输期间被从树中移除,则我们希望丢弃回复而不是调用 setState 更新我们的非存在的外观。
if (!mounted) return;
setState(() {
results.addOrUpdatePasswordStatus.message = addOrUpdatePasswordSuccessful;
results.addOrUpdatePasswordStatus.status =
addOrUpdatePasswordSuccessful == 'Success';
});
}
Future<void> _triggerFindPassword(
_PluginFunctionResults results, String? expected,
{String? application, String? key}) async {
String findPasswordSuccessful;
bool isSuccess = false;
try {
key ??= exampleUsername;
final passwordValue = await _keychainAccessPlugin.findSecureData(key,
application: application);
isSuccess = passwordValue == expected;
findPasswordSuccessful =
isSuccess ? "$passwordValue" : "Failed";
} on PlatformException catch (e) {
findPasswordSuccessful = 'Failed to findPassword $e';
}
// 如果小部件在异步平台消息传输期间被从树中移除,则我们希望丢弃回复而不是调用 setState 更新我们的非存在的外观。
if (!mounted) return;
setState(() {
final result = _PluginFunctionResult();
result.message = findPasswordSuccessful;
result.status = isSuccess;
results.findPasswordStatuses.add(result);
});
}
Future<void> _triggerDeletePassword(_PluginFunctionResults results,
{String? application}) async {
String deletePasswordSuccessful;
try {
deletePasswordSuccessful = await _keychainAccessPlugin
.deleteSecureData(exampleUsername, application: application)
? "Success"
: "Failed";
} on PlatformException catch (e) {
deletePasswordSuccessful = 'Failed to deletePassword $e';
}
// 如果小部件在异步平台消息传输期间被从树中移除,则我们希望丢弃回复而不是调用 setState 更新我们的非存在的外观。
if (!mounted) return;
setState(() {
results.deletePasswordStatus.message = deletePasswordSuccessful;
results.deletePasswordStatus.status =
deletePasswordSuccessful == 'Success';
});
}
// 平台消息是异步的,所以我们初始化在一个异步方法中。
Future<void> initPlatformState() async {
// 简单结果。
await _triggerAddPassword(_simpleResults);
await _triggerFindPassword(_simpleResults, examplePassword);
await _triggerUpdatePassword(_simpleResults);
await _triggerFindPassword(_simpleResults, exampleUpdatedPassword);
await _triggerAddOrUpdatePassword(_simpleResults);
await _triggerFindPassword(_simpleResults, exampleAddOrUpdatePassword);
await _triggerDeletePassword(_simpleResults);
await _triggerFindPassword(_simpleResults, null, key: "DOES_NOT_EXIST");
// 应用名称结果。
await _triggerAddPassword(_resultsForApplicationName,
application: applicationName);
await _triggerFindPassword(_resultsForApplicationName, examplePassword,
application: applicationName);
await _triggerUpdatePassword(_resultsForApplicationName,
application: applicationName);
await _triggerFindPassword(
_resultsForApplicationName, exampleUpdatedPassword,
application: applicationName);
await _triggerAddOrUpdatePassword(_resultsForApplicationName,
application: applicationName);
await _triggerFindPassword(
_resultsForApplicationName, exampleAddOrUpdatePassword,
application: applicationName);
await _triggerDeletePassword(_resultsForApplicationName,
application: applicationName);
await _triggerFindPassword(_resultsForApplicationName, null,
application: applicationName, key: "DOES_NOT_EXIST");
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Keychain Access Plugin Example'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Table(
border: TableBorder.all(),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
TableCell(child: columnField('Function Name', heading: true)),
TableCell(
child: columnField('No Application field', heading: true),
),
TableCell(
child: columnField('W/ Application', heading: true),
),
],
),
TableRow(
children: [
TableCell(
child: columnField('addSecureData'),
),
TableCell(
child: columnField(_buildStatusMessage(
[_simpleResults.addPasswordStatus])),
),
TableCell(
child: columnField(_buildStatusMessage(
[_resultsForApplicationName.addPasswordStatus])),
),
],
),
TableRow(
children: [
TableCell(
child: columnField('updateSecureData'),
),
TableCell(
child: columnField(_buildStatusMessage(
[_simpleResults.updatePasswordStatus])),
),
TableCell(
child: columnField(_buildStatusMessage(
[_resultsForApplicationName.updatePasswordStatus])),
),
],
),
TableRow(
children: [
TableCell(
child: columnField('addOrUpdateSecureData'),
),
TableCell(
child: columnField(_buildStatusMessage(
[_simpleResults.addOrUpdatePasswordStatus])),
),
TableCell(
child: columnField(_buildStatusMessage([
_resultsForApplicationName.addOrUpdatePasswordStatus
])),
),
],
),
TableRow(
children: [
TableCell(
child: columnField('findSecureData'),
),
TableCell(
child: columnField(_buildStatusMessage(
_simpleResults.findPasswordStatuses)),
),
TableCell(
child: columnField(_buildStatusMessage(
_resultsForApplicationName.findPasswordStatuses)),
),
],
),
TableRow(
children: [
TableCell(
child: columnField('deleteSecureData'),
),
TableCell(
child: columnField(_buildStatusMessage(
[_simpleResults.deletePasswordStatus])),
),
TableCell(
child: columnField(_buildStatusMessage(
[_resultsForApplicationName.deletePasswordStatus])),
),
],
),
]),
),
));
}
Widget columnField(String text, {bool heading = false}) {
TextStyle style = const TextStyle();
if (heading) {
style = const TextStyle(fontWeight: FontWeight.bold);
}
return Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
text,
style: style,
),
);
}
String _buildStatusMessage(List<_PluginFunctionResult> statuses) {
if (statuses.isEmpty) {
return '';
}
bool allSuccess = statuses
.map((e) => e.status)
.reduce((value, element) => value && element);
final message = statuses.map((e) => e.message).join('\n');
if (allSuccess) {
return '✅$message';
}
return '❌$message';
}
}
class _PluginFunctionResults {
_PluginFunctionResult addPasswordStatus = _PluginFunctionResult();
_PluginFunctionResult updatePasswordStatus = _PluginFunctionResult();
_PluginFunctionResult addOrUpdatePasswordStatus = _PluginFunctionResult();
final List<_PluginFunctionResult> findPasswordStatuses = [];
_PluginFunctionResult deletePasswordStatus = _PluginFunctionResult();
}
class _PluginFunctionResult {
bool status = false;
String message = 'Unknown';
}
更多关于Flutter密钥存储管理插件keychain_access的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter密钥存储管理插件keychain_access的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用keychain_access
插件进行密钥存储管理的代码示例。keychain_access
插件主要用于iOS平台上的密钥存储,而Android平台则使用其他机制(如keystore
),但本示例将专注于iOS平台。
首先,确保你已经在pubspec.yaml
文件中添加了keychain_access
依赖:
dependencies:
flutter:
sdk: flutter
keychain_access: ^0.6.0 # 请检查最新版本号
然后,运行flutter pub get
来获取依赖。
接下来,我们编写Flutter代码来演示如何使用keychain_access
插件。以下是一个简单的示例,展示如何存储和检索密钥。
main.dart
import 'package:flutter/material.dart';
import 'package:keychain_access/keychain_access.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Keychain Access Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: KeychainDemoPage(),
);
}
}
class KeychainDemoPage extends StatefulWidget {
@override
_KeychainDemoPageState createState() => _KeychainDemoPageState();
}
class _KeychainDemoPageState extends State<KeychainDemoPage> {
final Keychain _keychain = Keychain();
String _retrievedValue = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Keychain Access Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Value to Store'),
onChanged: (value) async {
await _storeValue(value);
},
),
SizedBox(height: 20),
Text('Retrieved Value: $_retrievedValue'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
_retrievedValue = await _retrieveValue();
setState(() {});
},
child: Text('Retrieve Value'),
),
],
),
),
);
}
Future<void> _storeValue(String value) async {
try {
await _keychain.setInternetCredentials(
server: 'example.com', // 可以是任何你选择的标识符
account: 'user_account',
password: value,
);
print('Value stored successfully');
} catch (e) {
print('Error storing value: $e');
}
}
Future<String?> _retrieveValue() async {
try {
final credentials = await _keychain.getInternetCredentials(
server: 'example.com',
account: 'user_account',
);
return credentials?.password;
} catch (e) {
print('Error retrieving value: $e');
return null;
}
}
}
注意事项
- 平台特定性:
keychain_access
主要用于iOS。对于Android,你需要使用其他插件或原生代码来访问keystore
。 - 权限:确保你的iOS项目已正确配置以使用Keychain服务。通常,这不需要额外的配置,但在某些情况下,你可能需要处理App Transport Security (ATS) 设置。
- 错误处理:示例代码中包含了基本的错误处理,但在实际应用中,你可能需要更详细的错误日志记录和用户反馈。
- 敏感数据:确保你存储的数据是加密的,并且只存储必要的敏感信息。
这个示例展示了如何在Flutter应用中存储和检索密钥,但请根据你的具体需求和安全要求进行调整。