التخزين الآمن باستخدام flutter_secure_storage
التخزين الآمن باستخدام flutter_secure_storage
تحتاج معظم تطبيقات Flutter إلى تخزين بيانات حساسة بصورة دائمة بين الجلسات — رموز المصادقة، ومفاتيح API، ورموز التحديث، وبيانات اعتماد المستخدم. تخزين هذه البيانات كنص عادي (على سبيل المثال عبر SharedPreferences) يُشكّل خطرًا أمنيًا جسيمًا. تحلّ هذه المشكلة حزمة flutter_secure_storage بكتابة البيانات إلى المخزن الآمن على مستوى نظام التشغيل: iOS Keychain على أجهزة Apple وAndroid Keystore المدعوم بتشفير على مستوى الأجهزة في Android.
SharedPreferences أزواج المفاتيح والقيم في ملف XML على Android (مجلد shared_prefs/) وفي NSUserDefaults على iOS. كلاهما نص عادي ويمكن قراءته من قِبل أي شخص لديه وصول إلى نظام ملفات الجهاز أو نسخة احتياطية. لا تخزّن أبدًا الرموز أو كلمات المرور فيهما.المقارنة بين SharedPreferences و flutter_secure_storage
فهم الفرق بينهما أمر بالغ الأهمية قبل اختيار آلية التخزين:
- SharedPreferences — غير مشفّر، مناسب للتفضيلات غير الحساسة (المظهر، اللغة، علامات الإعداد الأولي).
- flutter_secure_storage — مشفّر بـ AES-256 على Android (عبر Keystore)، وKeychain على iOS. مناسب للرموز وكلمات المرور وأسرار API.
- على Android، يُشفَّر المحتوى بمفتاح مخزَّن في Android Keystore ولا يغادر الأجهزة الآمنة على الأجهزة المدعومة.
- على iOS، تُخزَّن العناصر في Keychain مع خيار إمكانية الوصول الذي تُهيّئه (مثل الوصول فقط عند إلغاء قفل الجهاز).
إضافة التبعية
أضف الحزمة إلى pubspec.yaml:
pubspec.yaml
dependencies:
flutter_secure_storage: ^9.2.2
على Android يجب ضبط الحد الأدنى لإصدار SDK إلى 18 في android/app/build.gradle:
android/app/build.gradle
android {
defaultConfig {
minSdkVersion 18
// ...
}
}
واجهة برمجة التطبيقات الأساسية: القراءة والكتابة والحذف
الواجهة البرمجية بسيطة عن قصد. جميع الطرق async وتُعيد Future. أنشئ مثيلًا واحدًا من FlutterSecureStorage (أو استخدم نمط Singleton أو الحقن):
عمليات CRUD الأساسية
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class TokenRepository {
final FlutterSecureStorage _storage = const FlutterSecureStorage();
static const _accessTokenKey = 'access_token';
static const _refreshTokenKey = 'refresh_token';
// الكتابة / الكتابة فوق قيمة موجودة
Future<void> saveTokens({
required String accessToken,
required String refreshToken,
}) async {
await _storage.write(key: _accessTokenKey, value: accessToken);
await _storage.write(key: _refreshTokenKey, value: refreshToken);
}
// القراءة (تُعيد null إذا لم يكن المفتاح موجودًا)
Future<String?> getAccessToken() async {
return _storage.read(key: _accessTokenKey);
}
// التحقق من الوجود دون قراءة القيمة
Future<bool> hasSession() async {
final token = await _storage.read(key: _accessTokenKey);
return token != null && token.isNotEmpty;
}
// حذف مفتاح واحد
Future<void> deleteAccessToken() async {
await _storage.delete(key: _accessTokenKey);
}
// مسح جميع الإدخالات التي كتبها هذا التطبيق (استخدمها عند تسجيل الخروج)
Future<void> clearAll() async {
await _storage.deleteAll();
}
}
خيارات خاصة بكل منصة
يمكنك تخصيص السلوك لكل منصة بتمرير كائنات الخيارات إلى المُنشئ. هذا مهم للتطبيقات الإنتاجية التي تحتاج سياسات وصول أو مصادقة محددة:
خيارات Android و iOS
final storage = FlutterSecureStorage(
// Android: طلب مصادقة المستخدم قبل قراءة المفتاح
aOptions: const AndroidOptions(
encryptedSharedPreferences: true, // يستخدم واجهة برمجة EncryptedSharedPreferences
),
// iOS: العنصر متاح فقط عند إلغاء قفل الجهاز
iOptions: const IOSOptions(
accessibility: KeychainAccessibility.unlocked,
),
);
// القراءة بنفس كائن الخيارات
Future<String?> readSensitiveKey(String key) async {
return storage.read(key: key);
}
AndroidOptions (مثل تبديل encryptedSharedPreferences) بعد كتابة بيانات بالفعل، تصبح الإدخالات القديمة غير قابلة للقراءة لأن مخطط التشفير مختلف. قم دائمًا بترحيل التخزين الحالي أو مسحه عند تغيير الخيارات في تحديث إنتاجي.تخزين حالة المصادقة واستعادتها عند بدء التطبيق
نمط شائع هو التحقق من وجود رمز مخزَّن عند إطلاق التطبيق وإعادة التوجيه إلى الشاشة الرئيسية إذا كانت الجلسة لا تزال صالحة:
نمط Splash / Auth Gate
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
Future<bool> _checkSession() async {
const storage = FlutterSecureStorage();
final token = await storage.read(key: 'access_token');
return token != null && token.isNotEmpty;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: _checkSession(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final isLoggedIn = snapshot.data ?? false;
return isLoggedIn
? const HomeScreen()
: const LoginScreen();
},
);
}
}
أفضل ممارسات الأمان
- استدعِ دائمًا
deleteAll()عند تسجيل الخروج — لا تترك أبدًا رموزًا في التخزين بعد انتهاء الجلسة. - فضّل رموز الوصول قصيرة الصلاحية مع رموز التحديث، مع التدوير عند كل استخدام.
- لا تخزّن كلمات مرور بنص عادي — استخدم الرموز أو بيانات الاعتماد المشفّرة فقط.
- اجمع مع
local_auth(القياسات الحيوية) لإضافة طبقة أمان إضافية قبل القراءة من Keystore/Keychain. - على Android، اضبط
encryptedSharedPreferences: trueلاستخدام واجهة برمجةEncryptedSharedPreferencesمن Jetpack Security بدلًا من مخطط RSA + AES القديم.
FlutterSecureStorage الحقيقي. تغليف الحزمة خلف واجهة مجردة (مثل abstract class SecureStorage) يجعل المستودع قابلًا للاختبار 100% دون لمس Keychain/Keystore الفعلي على الجهاز.الخلاصة
flutter_secure_storage هي الحل القياسي لتخزين البيانات الحساسة في Flutter. تُغلّف iOS Keychain وAndroid Keystore خلف واجهة برمجية async نظيفة. استخدم write وread وdelete وdeleteAll لإدارة دورة حياة الأسرار كاملةً. فضّلها دائمًا على SharedPreferences لأي بيانات لا تريد لمهاجم محتمل قراءتها، واستدعِ deleteAll() عند تسجيل الخروج لعدم ترك أي بيانات اعتماد على الجهاز.