Microsoft.AspNetCore.Identity框架自带的IPasswordHasher密码加密验证方法

本文讨论的.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("密码错误,请重新输入");
            }

 

posted @ 2022-03-21 10:06  海~~D  阅读(1829)  评论(0编辑  收藏  举报