Где хранить токены?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где хранить токены аутентификации в Android-приложении
Хранение токенов — критически важный аспект безопасности мобильного приложения. Неправильный подход может привести к утечке конфиденциальных данных пользователя. Рассмотрим основные варианты, их плюсы, минусы и рекомендации.
Недопустимые способы хранения
Сначала перечислю категорически неприемлемые места:
- В
SharedPreferencesбез шифрования — файлы легко доступны на рутированном устройстве. - В
Intentкак extras — могут попасть в логи или быть перехвачены. - В статических переменных класса — данные теряются при убийстве процесса и небезопасны.
- В
SQLiteбазе без шифрования — аналогичноSharedPreferences. - В виде константы в коде — токен одинаков для всех пользователей и становится доступен при декомпиляции.
Рекомендуемые способы хранения
1. Android Keystore System + EncryptedSharedPreferences (API 23+)
Наиболее предпочтительный современный подход. Keystore предоставляет аппаратно-защищенное хранилище криптографических ключей, которые практически невозможно извлечь. Связка EncryptedSharedPreferences (из библиотеки Security Crypto) автоматически использует Keystore для шифрования данных.
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
val sharedPrefs = EncryptedSharedPreferences.create(
"secret_shared_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Запись токена
sharedPrefs.edit().putString("ACCESS_TOKEN", "your.jwt.token").apply()
// Чтение токена
val token = sharedPrefs.getString("ACCESS_TOKEN", null)
Преимущества:
- Высокий уровень безопасности.
- Ключи хранятся в доверенной среде (TEE/SE).
- Простой API, похожий на обычные
SharedPreferences. - Не требует ручного управления шифрованием.
2. Библиотека Security Crypto (DataStore)
Для современных приложений, использующих реактивный подход и Kotlin coroutines/Flow, можно использовать EncryptedDataStore.
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.security.crypto.EncryptedDataStore
val dataStore: DataStore<Preferences> = EncryptedDataStore.create(
context,
"tokens.pb"
)
suspend fun saveToken(token: String) {
dataStore.edit { preferences ->
preferences[ACCESS_TOKEN_KEY] = token
}
}
val accessTokenFlow: Flow<String?> = dataStore.data
.map { preferences -> preferences[ACCESS_TOKEN_KEY] }
3. Биометрическая аутентификация + Keystore
Для максимальной безопасности, когда доступ к токену должен быть защищен биометрией или PIN-кодом пользователя.
Принцип работы:
- Создается ключ в Android Keystore с настройкой
setUserAuthenticationRequired(true). - Токен шифруется с помощью этого ключа и сохраняется в
SharedPreferences. - Для расшифровки необходимо пройти биометрическую аутентификацию, которая "разблокирует" ключ.
Этот подход идеален для хранения Refresh Token или других наиболее чувствительных данных.
Архитектурные рекомендации
- Разделение токенов: Access Token (короткоживущий) можно хранить в памяти (ViewModel), а Refresh Token (долгоживущий) — в максимально защищенном хранилище (Keystore с биометрией).
- Использование Dependency Injection: Создайте интерфейс
TokenStorageдля инверсии зависимостей. Это упростит тестирование и возможную смену реализации. - Очистка токенов: Всегда предусматривайте механизм безопасного удаления токенов при логауте.
- Миграция: Если в приложении уже используется незащищенное хранение, реализуйте поэтапную миграцию на безопасное.
// Пример интерфейса для безопасного хранилища
interface SecureTokenStorage {
suspend fun saveAccessToken(token: String)
suspend fun getAccessToken(): String?
suspend fun saveRefreshToken(token: String)
suspend fun getRefreshToken(): String?
suspend fun clearTokens()
}
// Реализация через EncryptedSharedPreferences
class EncryptedTokenStorage @Inject constructor(
private val context: Context
) : SecureTokenStorage {
// ... реализация методов
}
Вывод и итоговая стратегия
Текущий best practice для новых приложений:
- API 23+ (Android 6.0+): Используйте
EncryptedSharedPreferencesиз библиотекиandroidx.security:security-crypto. Это самый простой и безопасный вариант по умолчанию. - Для критически важных данных (банкинг, мед.приложения): Добавьте слой биометрической аутентификации поверх Keystore.
- Архитектура: Инкапсулируйте логику работы с токенами в отдельный компонент (репозиторий, DataSource), реализующий интерфейс
TokenStorage.
Правильное хранение токенов — это баланс между безопасностью, удобством пользователя и производительностью. Всегда отдавайте предпочтение решениям, которые используют аппаратные возможности устройства (Keystore), а не программную шифрацию.