You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Web API密码重置后用户无法登录问题求助

ASP.NET Core Identity 自助重置密码失败问题修复

问题现象

在基于Entity Framework和Identity的ASP.NET Core Web API中,实现用户自助重置密码功能:用户输入邮箱,系统生成新密码并更新数据库,再发送邮件通知。但执行后数据库AspNetUsers表的PasswordHash列未更新,用户登录时提示Invalid Credentials。

问题根源

  1. 用户实例跟踪冲突:同时通过DbContext和UserManager获取用户实例,修改的是DbContext实例,但UserManager.UpdateAsync操作的实例未被正确跟踪,导致更新未持久化。
  2. 手动设置密码哈希不规范:直接赋值PasswordHash未遵循Identity的标准流程,可能导致哈希生成不符合验证逻辑,或上下文未标记属性变更。
  3. 冗余查询与数据不一致:多次重复查询用户信息,可能获取到不同状态的实例,引发逻辑错误。
  4. 事务混用问题:TransactionScope与EF的SaveChanges混用,可能导致事务未正确提交。

修复方案

  1. 统一使用UserManager处理用户操作,避免直接通过DbContext修改Identity用户。
  2. 改用UserManager.SetPasswordAsync方法设置新密码,该方法自动处理哈希生成和上下文更新。
  3. 移除冗余查询,直接从UserManager获取的实例中提取所需属性。
  4. 调整事务管理,优先使用EF内置事务或依赖UserManager的事务一致性。

修正后的代码

模型代码(无变更)

public class MerchantResetPasswordDto
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }
}

重置密码方法修正版

public async Task<Response<string>> UserResetUserPasswordByEmailAsync(MerchantResetPasswordDto model)
{
    try
    {
        var response = new Response<string>();
        // 直接通过UserManager按邮箱查找用户
        var userByName = await _userManager.FindByEmailAsync(model.Email);

        if (userByName == null)
        {
            _logger.Information("User Not Found");
            response.Message = "User Not Found";
            response.StatusCode = (int)HttpStatusCode.NotFound;
            response.Successful = false;
            return response;
        }

        var loggedUserRoles = await _userManager.GetRolesAsync(userByName);
        if (!loggedUserRoles.Any(r => r == "Merchant" || r == "MerchantUser"))
        {
            _logger.Error("Unauthorized");
            response.Data = null;
            response.StatusCode = (int)HttpStatusCode.BadRequest;
            response.Message = "Unauthorized";
            response.Successful = false;
            return response;
        }

        if (!userByName.IsActive)
        {
            _logger.Error("Unauthorized");
            response.Data = null;
            response.StatusCode = (int)HttpStatusCode.BadRequest;
            response.Message = "Unauthorized";
            response.Successful = false;
            return response;
        }

        // 生成随机密码
        var rng = new Random();
        ulong randomNumber = rng.NextULong(1000, 9999);
        var merchantPassword = "@Password" + randomNumber.ToString();

        // 使用UserManager的SetPasswordAsync设置新密码(自动处理哈希)
        var setPasswordResult = await _userManager.SetPasswordAsync(userByName, merchantPassword);
        if (!setPasswordResult.Succeeded)
        {
            var errors = string.Join(", ", setPasswordResult.Errors.Select(e => e.Description));
            _logger.Error($"Failed to set password: {errors}");
            response.Message = $"Failed to set password: {errors}";
            response.StatusCode = (int)HttpStatusCode.BadRequest;
            response.Successful = false;
            response.Data = $"Failed to set password: {errors}";
            return response;
        }

        // 审计日志
        var audit = new AuditReport()
        {
            AuditType = "Password Reset",
            ActionPerformed = $"Successfully Reset User Password: {userByName.UserName}",
            UserId = userByName.Id,
            SystemIpAddress = ipAddress.ToString(),
        };

        await _unitOfWork.AdminAuditReports.InsertAsync(audit);
        await _unitOfWork.Save();

        // 发送邮件
        var mailBody = await EmailBodyBuilder.GetMerchantUserSelfPasswordResetEmailBody(userByName, merchantPassword, emailTempPath: "wwwroot/files/Html/MerchantUserSelfResetPasswordEmail.html");
        var mailRequest = new MailRequest()
        {
            Subject = "Password Reset Notification",
            Body = mailBody,
            ToEmail = userByName.Email
        };

        bool emailResult = await _mailService.SendEmailAsync(mailRequest);

        if (emailResult)
        {
            _logger.Information("Password Reset Successfully Done");
            response.Message = "Password Reset Successfully Done";
            response.StatusCode = (int)HttpStatusCode.OK;
            response.Successful = true;
            response.Data = "Password Reset Successfully Done!";
        }
        else
        {
            _logger.Information("Email Service not available");
            response.Message = "Email Service not available";
            response.StatusCode = (int)HttpStatusCode.BadRequest;
            response.Successful = false;
            response.Data = "Email Service not available!";
        }

        return response;
    }
    catch (Exception ex)
    {
        string errorMessage = ex.Message ?? "An error occurred while resetting the user password.";
        _logger.Error("Failed to Reset User Password: " + errorMessage);

        var errorResponse = new Response<string>
        {
            Successful = false,
            StatusCode = (int)HttpStatusCode.InternalServerError,
            Message = "An error occurred while resetting the user password.",
            Data = null
        };

        return errorResponse;
    }
}

关键修复点说明

  • 统一用户实例:通过_userManager.FindByEmailAsync直接获取用户,避免DbContext和UserManager的实例冲突。
  • 标准密码设置流程:使用SetPasswordAsync替代手动赋值PasswordHash,确保密码哈希符合Identity验证规则,同时自动处理上下文更新。
  • 简化逻辑:移除冗余的数据库查询,直接从userByName实例获取角色、激活状态、ID等信息。
  • 移除TransactionScope:改用EF内置事务(若需跨操作事务,可通过_dbContext.Database.BeginTransactionAsync实现),避免事务混用问题。

内容的提问来源于stack exchange,提问作者Ayobamilaye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 11:35:34