从 ASP.NET 成员身份切换到 ASP.NET 核心身份时维护密码

Maintain passwords while switching from ASP.NET Membership to ASP.NET Core Identity

我的公司正计划将我们的应用程序从 .NET Framework 升级到 .NET Core,并作为升级的一部分从 ASP.NET 成员资格升级到 ASP.NET 核心身份服务器。我在 here.

上找到了一篇有用的文章

然而,有一个具有重大意义的子注释:

After completion of this script, the ASP.NET Core Identity app created earlier is populated with Membership users. Users need to change their passwords before logging in.

作为此次迁移的一部分,我们不能要求 600,000 名用户更改密码。但是,会员密码是单向散列的,因此我们无法检索它们然后再迁移它们。所以我想知道我们将如何使用新的 Identity Server 方法维护现有用户的密码。

我最近才这样做。

我们有一个遗留的 .net 会员系统,需要将大约 1 万名用户导入 asp.net 身份。当我从系统中复制所有用户时,我首先在 asp .net 身份核心用户 table 中创建了一个额外的列,我将他们的旧密码带走了。

然后是用户第一次登录的时候。我首先检查了旧密码是否存在,如果存在,然后我验证了它们并在 asp 上更新了密码。 net identity core 并删除了旧密码。这样所有的用户都在不知不觉中把密码移植到了新系统上。

我将尝试解释我是如何做到的,但代码有点疯狂。

我实际上在applicationuser中添加了两列table

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;
            }
        }
    }

重要提示:[SecurityStamp] 不能为 NULL!

我们最近从各种遗留系统迁移过来,因为它们都使用各种形式的散列密码,而不是尝试移植该逻辑,我们定制了密码验证代码以允许它调用 API 由每个遗留系统公开。从这样的系统迁移的每个用户都存储了 API URL。

当迁移的用户首次登录时,我们会调用所述服务(它本身使用不记名令牌和受限集成范围进行保护)以进行首次密码身份验证。如果我们得到成功响应,那么我们将以我们自己的格式对密码进行散列处理,并将永远使用下去。

这样做的缺点是您必须几乎永远保持旧系统(使用这个新的 API 螺栓)。因为它都是.Net,你可以通过将它全部保留在进程中并将迁移的用户散列密码复制到你的新数据库来更好,假设你可以在.Net Core 中实现旧的散列方案运行。