TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3071 2.08K
День 2554. #ЗаметкиНаПолях
Миграция Процесса Хеширования Паролей без Простоев. Окончание

Начало

В .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
  • 👍 10
More from @netdeveloperdiary
  1. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
  2. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  3. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  4. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  5. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  6. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →