Миграция Процесса Хеширования Паролей без Простоев. Окончание
Начало
В .NET 8 представлена регистрация сервисов по ключу, идеально подходящая для этого сценария. Можно зарегистрировать несколько реализаций одного и того же интерфейса и получать к ним доступ по имени.
1. Регистрация сервисов
Мы регистрируем оба сервиса хэширования, присваивая им уникальные ключи:
builder.Services.AddKeyedSingleton<IPasswordHasher, Pbdkf2PasswordHasher>("legacy");
builder.Services.AddKeyedSingleton<IPasswordHasher, Argon2PasswordHasher>("modern");
// (Опционально) Регистрируем новый сервис как вариант по умолчанию
builder.Services.AddSingleton<IPasswordHasher, Argon2PasswordHasher>();2. Обработка входа в систему
Теперь реализуем логику миграции. Внедряем оба сервиса, используя атрибут FromKeyedServices:
public class LoginCommandHandler(
IUserRepository userRepo,
[FromKeyedServices("modern")]
IPasswordHasher newHasher,
[FromKeyedServices("legacy")]
IPasswordHasher legacyHasher)
{
public async Task<AuthenticationResult>
Handle(LoginCommand command)
{
var user = await userRepo.GetAsync(command);
if (user is null)
return AuthenticationResult.Fail();
// 1. Пробуем новый алгоритм
if (newHasher.Verify(user.PasswordHash, command.Password))
return AuthenticationResult.Success(user);
// 2. При неудаче пробуем старый
if (legacyHasher.Verify(user.PasswordHash, command.Password))
{
// 3. МИГРАЦИЯ: вычисляем новый хэш и обновляем
var newHash = newHasher.Hash(command.Password);
user.UpdatePasswordHash(newHash);
await userRepo.SaveChangesAsync();
return AuthenticationResult.Success(user);
}
return AuthenticationResult.Fail();
}
}
Так пароли существующих пользователей постепенно будут обновляться, используя новый алгоритм.
Префиксы алгоритмов
Стандартные алгоритмы часто включают префикс (например, Bcrypt начинается с $2a$ или $2b$). Можно использовать это для выбора алгоритма вместо попыток валидации вслепую:
bool IsLegacyHash(string hash)
=> hash.StartsWith("pbkdf2$");
Завершение миграции
После некоторого времени (обычно несколько месяцев) большинство активных учётных записей будут обновлены. Затем вы можете запустить скрипт очистки, чтобы выявить оставшиеся устаревшие хэши и заставить этих пользователей сбросить свои пароли при следующей попытке входа в систему.
На этом этапе вы можете удалить:
- Регистрацию устаревшего сервиса хэширования;
- Код проверки старого хэша.
И миграция завершена.
Источник: https://www.milanjovanovic.tech/blog/a-practical-demo-of-zero-downtime-migrations-using-password-hashing