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: 当前实现不支持readAlldeleteAll方法。
  • 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-devlibjsoncpp-dev,运行应用时则需要libsecret-1-0libjsoncpp1作为依赖项。

Windows和macOS版本配置

  • Windows: 不支持readAlldeleteAll方法。
  • macOS: 需要在DebugProfile.entitlementsRelease.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

1 回复

更多关于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

  1. 导入插件

在你的Dart文件中导入插件:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
  1. 初始化存储

你可以在你的应用启动时初始化FlutterSecureStorage实例:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final storage = new FlutterSecureStorage();
  
  // 你可以在这里执行一些初始化操作,比如检查存储中是否有必要的数据
  // await storage.read(key: 'some_key');

  runApp(MyApp());
}
  1. 存储数据

你可以使用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');
  1. 读取数据

你可以使用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');
  1. 删除数据

你可以使用delete方法来删除数据:

Future<void> deleteUserData(String key) async {
  final storage = new FlutterSecureStorage();
  await storage.delete(key: key);
}

例如,删除用户令牌:

await deleteUserData('user_token');
  1. 完整示例

下面是一个完整的示例,展示了如何在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插件。

回到顶部