Сохранение паролей при переходе от членства в ASP.NET к основной идентификации ASP.NET - PullRequest
0 голосов
/ 07 декабря 2018

Моя компания планирует обновить наши приложения с .NET Framework до .NET Core и, как часть этого, обновить с членства ASP.NET до сервера ASP.NET Core Identity.Я нашел полезную статью по этому вопросу здесь .

Однако есть подпункт с серьезными последствиями:

После завершения этого сценария ASPПриложение .NET Core Identity, созданное ранее, заполняется пользователями Членства.Пользователи должны изменить свои пароли перед входом в систему.

Мы не можем просить 600 000 пользователей изменить свой пароль в рамках этой миграции.Однако пароли членства являются односторонними хэшированными, поэтому мы не можем получить их, а затем перенести.Поэтому мне интересно, как мы будем поддерживать пароли наших существующих пользователей с новым подходом Identity Server.

Ответы [ 2 ]

0 голосов
/ 07 декабря 2018

Мы недавно мигрировали из различных унаследованных систем, и, поскольку все они использовали различные формы хеширования паролей, вместо того, чтобы пытаться перенести эту логику, мы настроили код аутентификации пароля, чтобы он мог вызывать API, предоставляемыйкаждая устаревшая система.Каждый пользователь, мигрировавший из такой системы, имел URL-адрес API, сохраненный против него.

Когда мигрирующий пользователь впервые входит в систему, мы вызываем указанную службу (которая сама была защищена с помощью токена-носителя и ограниченной области интеграции), чтобы выполнить аутентификацию по паролю в первый раз.Если мы получим ответ об успешном завершении, то мы будем хэшировать пароль в нашем собственном формате, и он будет использоваться вечно.

Недостатком этого является то, что вам действительно нужно поддерживать старую систему (с этим новым API на болтах) почти вечно.Поскольку все это .Net, вы можете добиться большего, если будете держать все в рабочем состоянии и скопировать хешированные пароли перенесенных пользователей в свою новую БД, при условии, что вы сможете получить реализацию старой схемы хеширования, работающей внутри .Net Core.

0 голосов
/ 07 декабря 2018

Я сделал это совсем недавно.

У нас была устаревшая система членства .net, и нам нужно было импортировать около 10 тыс. Пользователей в удостоверение asp.net.Сначала я создал дополнительный столбец в таблице пользователей ядра идентификации asp .net, когда я скопировал всех пользователей из системы и привел их старый пароль.

Затем, когда пользователь вошел в систему в первый раз.Сначала я проверил, существует ли существующий пароль, если он существует, затем я проверил их на соответствие и обновил пароль на asp.Чистая идентификация ядра и удаление устаревшего пароля.Таким образом, все пользователи портировали свои пароли на новую систему, даже не осознавая этого.

Я собираюсь попытаться объяснить, как я это сделал, но код немного сумасшедший.

Iфактически добавил два столбца в таблицу applicationuser

public string LegacyPasswordHash { get; set; }
public string LegacyPasswordSalt { get; set; }

Метод ApplicationSignInManager -> CheckPasswordSignInAsync проверяет, является ли пользователь устаревшим пользователем

ApplicationSignInManager

public override async Task<SignInResult> CheckPasswordSignInAsync(ApplicationUser user, string password, bool lockoutOnFailure)
        {
        ........

            if (user.IsLegacy)
            {
                Logger.LogDebug(LoggingEvents.ApplicationSignInManagerCheckPasswordSignInAsync, "[user.Id: {user.Id}] is legacy.", user.Id);
                var results = await new LoginCommand(_logger, _userManager, user, password, lockoutOnFailure).Execute();
                if (results.Succeeded)
                {
                    await ResetLockout(user);
                    return SignInResult.Success;
                }
            }
            else if (await UserManager.CheckPasswordAsync(user, password))
            {
                await ResetLockout(user);
                return SignInResult.Success;
            }

            ........
        }

Команда входа в систему

 public class LoginCommand
    {
        private readonly ILogger _logger;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly ApplicationUser _user;
        private readonly string _password;
        private readonly bool _shouldLockout;

        public LoginCommand(ILogger logger, UserManager<ApplicationUser> userManager, ApplicationUser user, string password, bool shouldLockout)
        {
            _logger = logger;
            _userManager = userManager;
            _user = user;
            _password = password;
            _shouldLockout = shouldLockout;
        }

        public async Task<SignInResult> Execute()
        {
            _logger.LogInformation($"Found User: {_user.UserName}");
            if (_user.IsLegacy)
                return await new LegacyUserCommand(_logger, _userManager, _user, _password, _shouldLockout).Execute();
            if (await _userManager.CheckPasswordAsync(_user, _password))
                return await new CheckTwoFactorCommand(_logger, _userManager, _user).Execute();
            if (_shouldLockout)
            {
                return await new CheckLockoutCommand(_logger, _userManager, _user).Execute();
            }
            _logger.LogDebug($"Login failed for user {_user.Email} invalid password");
            return SignInResult.Failed;
        }
    }

LegacyUserCommand

  public class LegacyUserCommand
    {
        private readonly ILogger _logger;
        private readonly UserManager<ApplicationUser> _userManager;

        private readonly ApplicationUser _user;
        private readonly string _password;
        private bool _shouldLockout;

        public LegacyUserCommand(ILogger logger, UserManager<ApplicationUser> userManager, ApplicationUser user, string password, bool shouldLockout)
        {
            _logger = logger;
            _userManager = userManager;
            _user = user;
            _password = password;
            _shouldLockout = shouldLockout;
        }

        public async Task<SignInResult> Execute()
        {
            try
            {
                if (_password.EncodePassword(_user.LegacyPasswordSalt) == _user.LegacyPasswordHash)
                {
                    _logger.LogInformation(LoggingEvents.LegacyUserCommand, "Legacy User {_user.Id} migrating password.", _user.Id);
                    await _userManager.AddPasswordAsync(_user, _password);
                    _user.SecurityStamp = Guid.NewGuid().ToString();
                    _user.LegacyPasswordHash = null;
                    _user.LegacyPasswordSalt = null;
                    await _userManager.UpdateAsync(_user);
                    return await new CheckTwoFactorCommand(_logger, _userManager, _user).Execute();
                }
                if (_shouldLockout)
                {
                    _user.SecurityStamp = Guid.NewGuid().ToString();
                    await _userManager.UpdateAsync(_user);
                    _logger.LogInformation(LoggingEvents.LegacyUserCommand, "Login failed for Legacy user {_user.Id} invalid password. (LockoutEnabled)", _user.Id);
                    await _userManager.AccessFailedAsync(_user);
                    if (await _userManager.IsLockedOutAsync(_user))
                        return SignInResult.LockedOut;
                }

                _logger.LogInformation(LoggingEvents.LegacyUserCommand, "Login failed for Legacy user {_user.Id} invalid password", _user.Id);
                return SignInResult.Failed;
            }
            catch (Exception e)
            {
                _logger.LogError(LoggingEvents.LegacyUserCommand, "LegacyUserCommand Failed for [_user.Id: {_user.Id}]  [Error Message: {e.Message}]", _user.Id, e.Message);
                _logger.LogTrace(LoggingEvents.LegacyUserCommand, "LegacyUserCommand Failed for [_user.Id: {_user.Id}] [Error: {e}]", _user.Id, e);
                return SignInResult.Failed;
            }
        }
    }

TOP TIP: [SecurityStamp] не может быть NULL!

...