我不知道是否可以通过 Membership 处理此类迁移,而无需强制用户进行密码重置过程。
但是您可以通过同时将 Membership 迁移到 Asp.Net Identity 来做到这一点:Asp.Net Identity 具有扩展点,允许您处理“备用”密码签名匹配以支持旧签名。此时,您的登录名和密码仍然在内存中未散列,然后您可以顺便将签名转换为新格式。
blog 后面的所有详细信息和代码,包括 SQL 迁移,我只是在下面代码的附加注释中稍微解释一下。
这是实现此目的的主要类:
public class BackCompatPasswordHasher : PasswordHasher
{
public override string HashPassword(string password)
{
return base.HashPassword(password);
}
public override PasswordVerificationResult VerifyHashedPassword(
string hashedPassword, string providedPassword)
{
// Relies on SQL migration having formatted old hashes as
// (aspnet_Membership.Password + '|' +
// CAST(aspnet_Membership.PasswordFormat as varchar) + '|'
// + aspnet_Membership.PasswordSalt)
string[] passwordProperties = hashedPassword.Split('|');
if (passwordProperties.Length != 3)
{
return base.VerifyHashedPassword(hashedPassword,
providedPassword);
}
else
{
string passwordHash = passwordProperties[0];
int passwordformat = 1;
string salt = passwordProperties[2];
if (String.Equals(EncryptPassword(providedPassword,
passwordformat, salt),
passwordHash, StringComparison.CurrentCultureIgnoreCase))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
//This is copied from the existing SQL providers and is provided only
// for back-compat.
private string EncryptPassword(string pass, int passwordFormat,
string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm hm = HashAlgorithm.Create("SHA1");
if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length; )
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
}
return Convert.ToBase64String(bRet);
}
}
然后,在您的用户管理器中:
public class IdentityUserManager : UserManager<IdentityUser>
{
public IdentityUserManager(IUserStore<IdentityUser> store)
: base(store)
{
PasswordHasher = new BackCompatPasswordHasher();
}
}
在我的实际代码库中,我添加了一些用于处理重新散列的附加功能,但不幸的是,没有 cmets 说明原因。也许那是原始实现者的一些多余代码,或者确实需要。我没有调查它,所以这里是 IdentityUserManager 中的附加代码:
private ConcurrentDictionary<string, string> UserRehashed =
new ConcurrentDictionary<string, string>();
private bool CanRehash(IdentityUser user)
{
return UserRehashed.TryAdd(user.Id, user.Id);
}
protected async override Task<bool> VerifyPasswordAsync(
IUserPasswordStore<IdentityUser, string> store, IdentityUser user,
string password)
{
var hash = await store.GetPasswordHashAsync(user).ConfigureAwait(false);
var verifPassRes = PasswordHasher.VerifyHashedPassword(hash, password);
if (verifPassRes == PasswordVerificationResult.SuccessRehashNeeded &&
// avoid rehash loop.
CanRehash(user))
{
var chPassRes = await this.ChangePasswordAsync(user.Id,
password, password).ConfigureAwait(false);
if (!chPassRes.Succeeded)
{
// throw or log, whatever.
}
}
return verifPassRes != PasswordVerificationResult.Failed;
}