本文討論的.net版本為.NET6。眾所周知,.Net Identity框架中用戶的創建修改,密碼的驗證和修改都是在UserManager中完成的。
其中關於密碼的主要是以下幾個方法:
主要看ChangePasswordAsync方法內部實現:
/// <summary> /// Changes a user's password after confirming the specified <paramref name="currentPassword"/> is correct, /// as an asynchronous operation. /// </summary> /// <param name="user">The user whose password should be set.</param> /// <param name="currentPassword">The current password to validate before changing.</param> /// <param name="newPassword">The new password to set for the specified <paramref name="user"/>.</param> /// <returns> /// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> /// of the operation. /// </returns> public virtual async Task<IdentityResult> ChangePasswordAsync(TUser user, string currentPassword, string newPassword) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException(nameof(user)); } if (await VerifyPasswordAsync(passwordStore, user, currentPassword).ConfigureAwait(false) != PasswordVerificationResult.Failed) { var result = await UpdatePasswordHash(passwordStore, user, newPassword).ConfigureAwait(false); if (!result.Succeeded) { return result; } return await UpdateUserAsync(user).ConfigureAwait(false); } Logger.LogDebug(LoggerEventIds.ChangePasswordFailed, "Change password failed for user."); return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); }
其中 VerifyPasswordAsync(passwordStore, user, currentPassword) 方法是先驗證原來的密碼正確性,主要實現如下:
/// <summary> /// Returns a <see cref="PasswordVerificationResult"/> indicating the result of a password hash comparison. /// </summary> /// <param name="store">The store containing a user's password.</param> /// <param name="user">The user whose password should be verified.</param> /// <param name="password">The password to verify.</param> /// <returns> /// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="PasswordVerificationResult"/> /// of the operation. /// </returns> protected virtual async Task<PasswordVerificationResult> VerifyPasswordAsync(IUserPasswordStore<TUser> store, TUser user, string password) { var hash = await store.GetPasswordHashAsync(user, CancellationToken).ConfigureAwait(false); if (hash == null) { return PasswordVerificationResult.Failed; } return PasswordHasher.VerifyHashedPassword(user, hash, password); }
主要是 PasswordHasher.VerifyHashedPassword(user, hash, password) 的驗證方法。
/// <summary> /// Returns a <see cref="PasswordVerificationResult"/> indicating the result of a password hash comparison. /// </summary> /// <param name="user">The user whose password should be verified.</param> /// <param name="hashedPassword">The hash value for a user's stored password.</param> /// <param name="providedPassword">The password supplied for comparison.</param> /// <returns>A <see cref="PasswordVerificationResult"/> indicating the result of a password hash comparison.</returns> /// <remarks>Implementations of this method should be time consistent.</remarks> public virtual PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) { if (hashedPassword == null) { throw new ArgumentNullException(nameof(hashedPassword)); } if (providedPassword == null) { throw new ArgumentNullException(nameof(providedPassword)); } byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword); // read the format marker from the hashed password if (decodedHashedPassword.Length == 0) { return PasswordVerificationResult.Failed; } switch (decodedHashedPassword[0]) { case 0x00: if (VerifyHashedPasswordV2(decodedHashedPassword, providedPassword)) { // This is an old password hash format - the caller needs to rehash if we're not running in an older compat mode. return (_compatibilityMode == PasswordHasherCompatibilityMode.IdentityV3) ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Success; } else { return PasswordVerificationResult.Failed; } case 0x01: int embeddedIterCount; if (VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, out embeddedIterCount)) { // If this hasher was configured with a higher iteration count, change the entry now. return (embeddedIterCount < _iterCount) ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Success; } else { return PasswordVerificationResult.Failed; } default: return PasswordVerificationResult.Failed; // unknown format marker } }
首先會對已保存的hashPassword進行base64的解密,然后再將輸入密碼進行hash之后與base64解密的密碼進行比較,一致則成功。
注:這里雖然輸入了user對象,但是實際中並沒有用到。
再看 UpdatePasswordHash(passwordStore, user, newPassword) 方法中,
/// <summary> /// Updates a user's password hash. /// </summary> /// <param name="user">The user.</param> /// <param name="newPassword">The new password.</param> /// <param name="validatePassword">Whether to validate the password.</param> /// <returns>Whether the password has was successfully updated.</returns> protected virtual Task<IdentityResult> UpdatePasswordHash(TUser user, string newPassword, bool validatePassword) => UpdatePasswordHash(GetPasswordStore(), user, newPassword, validatePassword); private async Task<IdentityResult> UpdatePasswordHash(IUserPasswordStore<TUser> passwordStore, TUser user, string? newPassword, bool validatePassword = true) { if (validatePassword) { var validate = await ValidatePasswordAsync(user, newPassword).ConfigureAwait(false); if (!validate.Succeeded) { return validate; } } var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); return IdentityResult.Success; }
先會 ValidatePasswordAsync 驗證密碼的規則,如必須6位以上,英文數組組合等。然后再調用 PasswordHasher.HashPassword(user, newPassword) 方法加密。
/// <summary> /// Returns a hashed representation of the supplied <paramref name="password"/> for the specified <paramref name="user"/>. /// </summary> /// <param name="user">The user whose password is to be hashed.</param> /// <param name="password">The password to hash.</param> /// <returns>A hashed representation of the supplied <paramref name="password"/> for the specified <paramref name="user"/>.</returns> public virtual string HashPassword(TUser user, string password) { if (password == null) { throw new ArgumentNullException(nameof(password)); } if (_compatibilityMode == PasswordHasherCompatibilityMode.IdentityV2) { return Convert.ToBase64String(HashPasswordV2(password, _rng)); } else { return Convert.ToBase64String(HashPasswordV3(password, _rng)); } }
將密碼進行hash算法以后再做base64轉碼。這里面密碼主要算法是PBKDF2,有興趣可以自行了解(PBKDF2簡單而言就是將salted hash進行多次重復計算)。
注:這里雖然輸入了user對象,但是實際中也並沒有用到。
由此可知,這里的加密和驗證方法主要是 IPasswordHasher<TUser> 和其實現類 PasswordHasher<TUser>,而且user對象並沒有起到實際作用。如果我們在項目中要使用identity自帶的密碼驗證方法,只需注入相關依賴即可直接使用
builder.Services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
var hashPassword = _passwordHasher.HashPassword(user, password);//加密 var verfied = _passwordHasher.VerifyHashedPassword(user, user.Password, password); if (verfied != PasswordVerificationResult.Success) { return Result.Error("密碼錯誤,請重新輸入"); }