ASP.NET Core 8 Web API并发异常及密码重置无效问题求助
- 环境:VS 2022 + ASP.NET Core 8.0 Web API,集成Identity API,自定义了UserManager包装类
- DbContext配置:通过依赖注入注册
ExpIdentityDbContext(继承自IdentityDbContext),代码如下:
AddScoped<ExpIdentityDbContext>() .AddDbContext<ExpIdentityDbContext>(options => { options.UseSqlServer(configuration.GetConnectionString("ExpUsers")); options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTrackingWithIdentityResolution); });
- 额外配置:同一数据库存在另一个管理非Identity表的DbContext,启用Swagger调试接口
- 异常情况:
- 登录、创建/查询用户功能正常,但修改密码时抛出「Optimistic concurrency failure, object has been modified」异常
- 重置密码时提示「Invalid token」,已通过
userManager.GeneratePasswordResetTokenAsync生成令牌,但问题未解决
一、解决「Optimistic concurrency failure」异常
该问题核心是EF Core全局无跟踪配置与Identity并发机制冲突,可从以下几点调整:
移除重复的DbContext注册
Identity内部会自动处理IdentityDbContext的生命周期,同时用AddScoped和AddDbContext注册会导致同一请求内出现多个DbContext实例,引发跟踪冲突。保留AddDbContext配置,删除单独的AddScoped<ExpIdentityDbContext>()。为Identity操作启用实体跟踪
NoTrackingWithIdentityResolution是全局无跟踪配置,但Identity的用户更新操作需要实体跟踪来处理ConcurrencyStamp并发戳。可通过两种方式调整:- 单操作手动启用跟踪:
// 修改密码时,获取带跟踪的用户实例 var user = await _userManager.Users.AsTracking().FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { /* 处理用户不存在逻辑 */ } var result = await _userManager.ChangePasswordAsync(user, oldPassword, newPassword); - DbContext层面单独配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 为Identity用户实体强制启用跟踪 modelBuilder.Entity<IdentityUser>().HasQueryTrackingBehavior(QueryTrackingBehavior.TrackAll); }
- 单操作手动启用跟踪:
手动刷新并发戳
如果仍出现并发错误,可在修改前从数据库重新获取最新的用户并发戳:var user = await _userManager.FindByIdAsync(userId); if (user != null) { await _dbContext.Entry(user).ReloadAsync(); var result = await _userManager.ChangePasswordAsync(user, oldPassword, newPassword); }
二、解决「Invalid token」问题
重置密码令牌无效通常与用户状态、加密配置或令牌传递过程有关,排查方向:
确保令牌生成与验证用同一UserManager实例
检查UserManager包装类是否注册为Scoped生命周期,避免单例导致的状态不一致。同时确认生成令牌后,用户未修改过密码、邮箱等会触发SecurityStamp变更的信息。生成令牌前获取最新用户实例
必须从数据库获取最新的用户数据生成令牌,不能使用缓存或无跟踪的旧实例:var user = await _userManager.FindByEmailAsync(email); if (user == null) { /* 处理用户不存在逻辑 */ } var token = await _userManager.GeneratePasswordResetTokenAsync(user);检查DataProtection配置
Identity令牌依赖DataProtection服务,若应用重启或多实例部署,需配置持久化密钥避免令牌失效:// 在Program.cs中添加配置 builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"C:\AppKeys")) // 密钥持久化路径 .SetApplicationName("YourAppUniqueName"); // 确保不同环境应用名称一致单实例调试时,若生成令牌后重启过应用,旧令牌会因密钥重置失效,需重新生成。
处理令牌URL编码问题
通过Swagger传递令牌时,特殊字符(如+、/、=)可能被转义,验证前需解码:var decodedToken = HttpUtility.UrlDecode(token); var result = await _userManager.ResetPasswordAsync(user, decodedToken, newPassword);
内容的提问来源于stack exchange,提问作者user413088

