.NET 6控制台应用密码重置报错:未注册'Default'的IUserTwoFactorTokenProvider
解决.NET 6控制台应用中GeneratePasswordResetTokenAsync的"Default" TokenProvider未注册问题
问题根源分析
你遇到的错误核心是手动实例化UserManager时缺失TokenProvider依赖,再加上Identity服务注册混乱,导致系统找不到名为"Default"的令牌提供者:
- 重复注册
AddIdentity<IdentityUser, IdentityRole>()和AddIdentity<ApplicationUser, IdentityRole>(),造成配置冲突 - 手动
new UserManager<ApplicationUser>()时,未传入令牌提供者、日志等必要依赖 - 错误直接调用
GeneratePasswordResetTokenAsync(user).ToString(),未await异步方法,得到的是Task对象字符串而非实际令牌
具体修复步骤
1. 清理并正确注册Identity服务
删除重复的Identity注册,只保留针对ApplicationUser的配置。AddDefaultTokenProviders()已经默认注册了名为"Default"的DataProtectorTokenProvider,无需手动添加ProviderMap:
// 移除以下重复注册代码 // services.AddIdentity<IdentityUser, IdentityRole>(); // services.AddTransient<IUserStore<IdentityUser>, UserStore<IdentityUser>>(); // services.AddTransient<UserManager<IdentityUser>>(); // 仅保留ApplicationUser的Identity配置 services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // 该行已自动注册Default令牌提供者
2. 通过依赖注入获取UserManager,禁止手动实例化
手动new的UserManager缺少大量必要依赖(包括TokenProvider),必须从ServiceProvider获取:
// 先添加必要依赖再构建ServiceProvider services.AddLogging(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // 构建服务容器 IServiceProvider serviceProvider = services.BuildServiceProvider(); // 从容器获取UserManager,而非手动实例化 var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
3. 修正异步方法调用
GeneratePasswordResetTokenAsync是异步方法,必须用await获取结果,同时将相关方法改为异步:
// 将CheckingPasswords改为异步方法 public async Task CheckingPasswords() { try { var date = DateTime.Now.AddMonths(-3); var userList = _applicationContext.Users.Where(t => t.PasswordExpiry != null && t.PasswordExpiry <= DateTime.Now.AddMonths(-3) && t.Deleted == false && t.PortalAccessEnabled == true).ToList(); if (userList.Count > 0) { foreach (var user in userList) { user.PortalAccessEnabled = false; _applicationContext.Update(user); await _applicationContext.SaveChangesAsync(); // 同步方法改为异步 // 正确获取重置令牌 var code = await _userManager.GeneratePasswordResetTokenAsync(user); var url = "你的密码重置链接模板"; var callbackUrl = url.Replace("[[id]]", user.Id).Replace("[[code]]", code); Email email = new() { ID = 0, From = _appConfig.DefaultFromEmail, To = user.Email, IsHTML = true, QueuedAt = DateTime.Now, SendAt = DateTime.Now, Subject = Core.Resource.Portal.Pages.Common_PortalName + " - " + Core.Resource.Portal.Pages.User_ResetPassword_EmailSubject, Created = DateTime.Now, Body = string.Format(Core.Resource.Portal.Pages.User_ResetPassword_EmailBody, callbackUrl), TrackingID = Guid.NewGuid(), Status = 1 //Pending }; _dbContext.Add(email); await _dbContext.SaveChangesAsync(); // 同步方法改为异步 } } } catch(Exception ex) { // 建议添加异常处理逻辑 } } // 将Run方法改为异步 public async Task Run() { await CheckingPasswords(); }
4. 调整ReportAgent中的异步调用
因为Run方法改为异步,需要在调用处使用await:
protected async Task RunSystemJob(string jobName, string parameters, int commandTimeOutSecs, AgentConfigDTO _appConfig) { IReportJob job = null; switch (jobName.ToLower()) { case "checkpasswords": job = new CheckPasswords(_context, parameters, _applicationContext, _userManager, _appConfig); break; default: throw new Exception($"Cannot execute system job {jobName} as it cannot be found."); } if (job != null) { job.CommandTimeOut = commandTimeOutSecs; job._appConfig = _appConfig; await ((dynamic)job).Run(); // 或让IReportJob接口定义异步Run方法 } }
额外注意事项
- 确保
ApplicationUser继承自IdentityUser,否则Identity服务无法正确识别 - 控制台应用中使用Identity必须添加
IHttpContextAccessor,部分内部逻辑依赖该服务 - 数据库操作尽量使用异步方法,避免阻塞线程
内容的提问来源于stack exchange,提问作者Raluca Micu
相关产品推荐
相关产品推荐

