如何将SimpleMembership用户数据迁移到ASPNET核心标识

时间:2017-10-01 02:23:02

标签: authentication asp.net-core cryptography .net-core asp.net-identity

我有一个应用程序,我最初是在我年轻和愚蠢时构建的,它的身份验证是使用SimpleMembership框架设置的,所有用户数据都包含在webpages_Membership表中。我非常有兴趣通过SQL Server将我的后端重建为具有AspNetCore Identity的AspNetCore Web API,而不会丢失用户信息。

我很幸运能够提出SQL脚本将所有内容移到AspNetUsers表中,以准备使用Identity而不是SimpleMembership,但我遇到的问题是密码哈希。我从thisthis这样的文章中收集到,我最好的选择是覆盖PasswordHasher<IdentityUser>以复制SimpleMembership加密流,然后在密码进入时逐步迁移数据库。 / p>

问题是我无法找到如何在.NET Core中实现此流复制。上面链接的后一篇文章指出,SimpleMembership流程是通过System.Web.Helpers.Crypto包实现的,这个包似乎不存在于.NET Core库中,我无法弄清楚它的实现是否记录在任何地方。 (Its MSDN documentation表示它正在使用RFC2898哈希,但我对加密知之甚少,不知道它是否足以让它自行继续。这不是我的专业领域。 :()

非常感谢任何有关如何处理此问题的见解。谢谢!

1 个答案:

答案 0 :(得分:2)

对于可能遇到同样问题的其他人 - 我能够在GitHub上的某个地方找到System.Web.Helpers.Crypto代码的副本,并且或多或少地将其复制到自定义密码hasher类中:

public class CustomPasswordHasher : PasswordHasher<IdentityUser>
{
    public override PasswordVerificationResult VerifyHashedPassword(IdentityUser user, string hashedPassword,
        string providedPassword)
    {
        var isValidPasswordWithLegacyHash = VerifyHashedPassword(hashedPassword, providedPassword);
        return isValidPasswordWithLegacyHash
            ? PasswordVerificationResult.SuccessRehashNeeded
            : base.VerifyHashedPassword(user, hashedPassword, providedPassword);
    }

    private const int _pbkdf2IterCount = 1000;
    private const int _pbkdf2SubkeyLength = 256 / 8;
    private const int _saltSize = 128 / 8;

    public static bool VerifyHashedPassword(string hashedPassword, string password)
    {
        //Checks password using legacy hashing from System.Web.Helpers.Crypto
        var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
        if (hashedPasswordBytes.Length != (1 + _saltSize + _pbkdf2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
        {
            return false;
        }
        var salt = new byte[_saltSize];
        Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, _saltSize);
        var storedSubkey = new byte[_pbkdf2SubkeyLength];
        Buffer.BlockCopy(hashedPasswordBytes, 1 + _saltSize, storedSubkey, 0, _pbkdf2SubkeyLength);
        byte[] generatedSubkey;
        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, _pbkdf2IterCount))
        {
            generatedSubkey = deriveBytes.GetBytes(_pbkdf2SubkeyLength);
        }
        return ByteArraysEqual(storedSubkey, generatedSubkey);
    }

    internal static string BinaryToHex(byte[] data)
    {
        var hex = new char[data.Length * 2];
        for (var iter = 0; iter < data.Length; iter++)
        {
            var hexChar = (byte) (data[iter] >> 4);
            hex[iter * 2] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
            hexChar = (byte) (data[iter] & 0xF);
            hex[iter * 2 + 1] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
        }
        return new string(hex);
    }

    [MethodImpl(MethodImplOptions.NoOptimization)]
    private static bool ByteArraysEqual(byte[] a, byte[] b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }
        if (a == null || b == null || a.Length != b.Length)
        {
            return false;
        }
        var areSame = true;
        for (var i = 0; i < a.Length; i++)
        {
            areSame &= (a[i] == b[i]);
        }
        return areSame;
    }
}

此类重写VerifyHashedPassword并检查用户提供的密码是否与旧的加密哈希一起使用;如果是,则该方法返回PasswordVerificationResult.SuccessRehashNeeded。否则,它会将密码传递给基类的方法,并使用.NET Core散列行为将其验证为正常。

然后,您可以指示UserManager使用此密码hasher而不是默认值,方法是将其包含在Startup.cs中的依赖项注入配置中:

public class Startup 
{
...
    public void ConfigureServices(IServiceCollection services)
    {
    ...
        services.AddScoped<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
    }
...
} 

我最终的目的是让我的控制器在返回SuccessRehashNeeded结果时触发用户密码的重新搜索,从而允许所有用户逐渐迁移到正确的散列模式。