ASP.NET Core Web API密码重置后用户无法登录问题求助
ASP.NET Core Identity 自助重置密码失败问题修复
问题现象
在基于Entity Framework和Identity的ASP.NET Core Web API中,实现用户自助重置密码功能:用户输入邮箱,系统生成新密码并更新数据库,再发送邮件通知。但执行后数据库AspNetUsers表的PasswordHash列未更新,用户登录时提示Invalid Credentials。
问题根源
- 用户实例跟踪冲突:同时通过
DbContext和UserManager获取用户实例,修改的是DbContext实例,但UserManager.UpdateAsync操作的实例未被正确跟踪,导致更新未持久化。 - 手动设置密码哈希不规范:直接赋值
PasswordHash未遵循Identity的标准流程,可能导致哈希生成不符合验证逻辑,或上下文未标记属性变更。 - 冗余查询与数据不一致:多次重复查询用户信息,可能获取到不同状态的实例,引发逻辑错误。
- 事务混用问题:
TransactionScope与EF的SaveChanges混用,可能导致事务未正确提交。
修复方案
- 统一使用
UserManager处理用户操作,避免直接通过DbContext修改Identity用户。 - 改用
UserManager.SetPasswordAsync方法设置新密码,该方法自动处理哈希生成和上下文更新。 - 移除冗余查询,直接从
UserManager获取的实例中提取所需属性。 - 调整事务管理,优先使用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
相关产品推荐
相关产品推荐

