تأمين البيانات الحساسة باستخدام flutter_secure_storage
تأمين البيانات الحساسة باستخدام flutter_secure_storage
يخزن SharedPreferences العادي البيانات كنص عادي غير مشفر على نظام الملفات في الجهاز. هذا مقبول للإعدادات غير الحساسة، لكنه غير مناسب إطلاقاً لرموز المصادقة (tokens) ومفاتيح API وكلمات المرور وأي بيانات شخصية يجب الحفاظ على سريتها. تحل flutter_secure_storage هذه المشكلة بتفويض التخزين إلى المخزن الآمن المدمج في كل منصة مدعومة.
إضافة التبعية
أضف flutter_secure_storage إلى ملف pubspec.yaml:
pubspec.yaml
dependencies:
flutter_secure_storage: ^9.2.2
على Android، يجب تعيين الحد الأدنى لإصدار SDK إلى 23 في ملف android/app/build.gradle:
android/app/build.gradle — متطلب الحد الأدنى لإصدار SDK
android {
defaultConfig {
minSdkVersion 23 // مطلوب لـ EncryptedSharedPreferences
}
}
إنشاء المثيل وخيارات Android
أنشئ مثيلاً من FlutterSecureStorage مرة واحدة، ويُفضّل أن يكون singleton أو تبعية مُحقوقة. على Android يمكنك تمرير AndroidOptions للتحكم في مخطط التشفير وسلوك تخزين المفاتيح:
الإنشاء الأساسي مع خيارات Android
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// موصى به: أنشئ مرة واحدة وأعد الاستخدام
final _storage = FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true, // استخدم EncryptedSharedPreferences (الافتراضي true لـ API 23+)
),
);
// للاستخدام المتقدم يمكنك أيضاً توفير IOSOptions وLinuxOptions وغيرها
// final _storage = FlutterSecureStorage(
// iOptions: IOSOptions(accountName: 'myApp', accessibility: KeychainAccessibility.first_unlock),
// );
العمليات الأساسية (CRUD)
الواجهة البرمجية غير متزامنة بالكامل وتحاكي خريطة مفتاح/قيمة بسيطة. جميع العمليات تُعيد Future ويجب استخدام await معها داخل دالة async:
الكتابة والقراءة والحذف وقراءة الكل
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
// --- الكتابة ---
// يخزن (أو يُحدّث) قيمة للمفتاح المحدد
static Future<void> writeToken(String token) async {
await _storage.write(key: 'auth_token', value: token);
}
// --- القراءة ---
// يُعيد null إذا لم يكن المفتاح موجوداً
static Future<String?> readToken() async {
return _storage.read(key: 'auth_token');
}
// --- الحذف ---
// يحذف زوج مفتاح/قيمة واحد
static Future<void> deleteToken() async {
await _storage.delete(key: 'auth_token');
}
// --- حذف الكل ---
// يحذف كل مفتاح مخزن من قِبل هذا التطبيق (استخدمه عند تسجيل الخروج)
static Future<void> clearAll() async {
await _storage.deleteAll();
}
// --- قراءة الكل ---
// يُعيد Map<String, String> لجميع الأزواج المخزنة
static Future<Map<String, String>> readAll() async {
return _storage.readAll();
}
// --- التحقق من الوجود ---
static Future<bool> hasToken() async {
return _storage.containsKey(key: 'auth_token');
}
}
استخدام عملي: تخزين JWT واستعادته
من الأنماط الشائعة حفظ رمز JWT للوصول بعد تسجيل الدخول واستعادته عند تشغيل التطبيق. يوضح المثال أدناه خدمة AuthService مبسطة تُغلّف هذه العملية:
AuthService مع حفظ الرمز بشكل آمن
class AuthService {
static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
static const _tokenKey = 'jwt_access_token';
static const _refreshKey = 'jwt_refresh_token';
/// استدعِ هذا بعد استجابة API ناجحة لتسجيل الدخول
static Future<void> saveTokens({
required String accessToken,
required String refreshToken,
}) async {
await Future.wait([
_storage.write(key: _tokenKey, value: accessToken),
_storage.write(key: _refreshKey, value: refreshToken),
]);
}
/// يُعيد null عندما لم يسجل المستخدم الدخول قط أو سجّل الخروج
static Future<String?> getAccessToken() =>
_storage.read(key: _tokenKey);
/// احذف جميع بيانات الاعتماد عند تسجيل الخروج
static Future<void> logout() async {
await Future.wait([
_storage.delete(key: _tokenKey),
_storage.delete(key: _refreshKey),
]);
}
}
Future.wait([...]) عندما تحتاج إلى تنفيذ عمليات تخزين آمن متعددة ومستقلة في وقت واحد. يُشغّلها بالتوازي وينتظر اكتمال جميعها، مما يُقلل من إجمالي زمن الاستجابة مقارنةً بـ await المتسلسل.معالجة الأخطاء والحالات الحدية
احرص دائماً على لفّ استدعاءات التخزين الآمن في كتل try/catch في كود الإنتاج. يمكن أن يُطلق Keychain أو Keystore استثناءات إذا كان الجهاز في حالة غير معتادة (مثل أول تشغيل قبل إعداد بيانات اعتماد الجهاز، أو إعادة تثبيت التطبيق التي أزالت وصول Keychain):
قراءة دفاعية مع معالجة الأخطاء
Future<String?> safeReadToken() async {
try {
return await _storage.read(key: 'auth_token');
} catch (e) {
// سجّل الخطأ؛ لا تعرض الاستثناء الخام للمستخدم
debugPrint('SecureStorage read failed: $e');
// تعامل معه كأنه لا يوجد رمز — أجبر المستخدم على إعادة تسجيل الدخول
return null;
}
}
encryptedSharedPreferences بين إصدارات التطبيق (مثلاً من false إلى true)، تصبح البيانات المخزنة بالمخطط القديم غير قابلة للقراءة. خطّط لاستراتيجية التشفير قبل الإطلاق وقم بترحيل البيانات صراحةً إذا كان لا بد من تغييرها.ما يجب وما لا يجب تخزينه
- خزّن بشكل آمن: رموز OAuth ورموز JWT للوصول/التحديث ومفاتيح API وملفات تعريف الارتباط للجلسة وعلامات المقاييس الحيوية ومفاتيح التشفير.
- لا تخزّن في التخزين الآمن: البيانات الثنائية الكبيرة (ليس مصمماً لذلك)، والإعدادات غير الحساسة (استخدم
SharedPreferencesلها — التخزين الآمن أبطأ)، أو البيانات المؤقتة المشتقة التي يمكن إعادة جلبها.
ملخص
توفر flutter_secure_storage واجهة برمجية بسيطة لمفتاح/قيمة مدعومة بـ iOS Keychain وAndroid Keystore/EncryptedSharedPreferences. العمليات الخمس الأساسية — write وread وdelete وdeleteAll وreadAll — كلها غير متزامنة. على Android يجب تعيين minSdkVersion 23 وتفعيل AndroidOptions(encryptedSharedPreferences: true). احرص دائماً على معالجة الاستثناءات، وتجنب تخزين البيانات الكبيرة، واستدعِ deleteAll عند تسجيل الخروج لمسح جميع بيانات الاعتماد.