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

.NET 6控制台无DI使用UserManager生成重置令牌报错求助

.NET 6控制台应用手动构造UserManager生成重置令牌报错:No IUserTwoFactorTokenProvider named 'Default' is registered.

问题描述

在.NET 6控制台应用中尝试不依赖依赖注入(DI)直接构造UserManager<ApplicationUser>实例,成功创建实例后,调用GeneratePasswordResetTokenAsync生成密码重置令牌时抛出错误:

No IUserTwoFactorTokenProvider named 'Default' is registered.

初始化UserManager的代码(Program.cs)

IConfiguration config = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
         .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
         .AddEnvironmentVariables()
         .Build();

IServiceCollection services = new ServiceCollection();
services.AddTransient<UserManager<ApplicationUser>>();

var connectionStrings = config.GetSection("ConnectionStrings");
var messagingConnectionString = connectionStrings.GetValue<string>("Messaging");

AgentConfigDTO appSettingsDTO = new AgentConfigDTO()
        {
           ....
        };

services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(connectionStrings.ToString(),
            sqlServerOptionsAction: sqlOptions =>
            {
                sqlOptions.EnableRetryOnFailure(
                            maxRetryCount: 5,
                            maxRetryDelay: TimeSpan.FromSeconds(30),
                            errorNumbersToAdd: null);
            }
            ));

// Register UserManager & RoleManager
services.AddIdentity<IdentityUser, IdentityRole>();
services.AddIdentity<ApplicationUser, IdentityRole>(
            options =>
            {
                options.Tokens.ProviderMap.Add("Default", new TokenProviderDescriptor(typeof(IUserTwoFactorTokenProvider<ApplicationUser>)));
            })
           .AddEntityFrameworkStores<ApplicationDbContext>()
           .AddDefaultTokenProviders();

services.AddTransient<IUserStore<IdentityUser>, UserStore<IdentityUser>>();
services.AddTransient<UserManager<IdentityUser>>();

IServiceProvider serviceProvider = services.BuildServiceProvider();

var optionsBuilderApplication = new DbContextOptionsBuilder<ApplicationDbContext>();
var _applicationContext = new ApplicationDbContext(optionsBuilderApplication.UseSqlServer(connectionStrings.ToString()).Options);
     
IUserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_applicationContext);
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(store, null, new PasswordHasher<ApplicationUser>(), null, null, null, null, serviceProvider, null);

// UserManager & RoleManager require logging and HttpContext dependencies
services.AddLogging();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

new ReportAgent(appSettingsDTO, messagingConnectionString, userManager).RunAsConsole(args);

调用UserManager的方法代码

public void 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)
         {
             // Create email
             foreach (var user in userList)
             {
                 // Lock user out of the account
                 user.PortalAccessEnabled = false;
                 _applicationContext.Update(user);
                 _applicationContext.SaveChanges();

                 // Create email
                 // Exception is thrown on this line of code
                 var code = _userManager.GeneratePasswordResetTokenAsync(user).ToString(); 
                 var url = ""; // 原代码省略部分
                 var callbackUrl = url.Replace("[[id]]", user.Id).Replace("[[code]]", code);

                 Email email = new()
                        {
                            ... // 原代码省略部分
                        };
                 _dbContext.Add(email);
                 _dbContext.SaveChanges();
             }
         }
     }
}

已尝试的无效操作

手动添加Token Provider但未解决问题:

var identityBuilder = new IdentityBuilder(typeof(ApplicationUser), typeof(IdentityRole<string>), services);
identityBuilder.AddTokenProvider("Default", typeof(DataProtectorTokenProvider<ApplicationUser>));
services.AddTransient<UserManager<ApplicationUser>>();

解决方案

核心问题分析

  1. 重复注册Identity服务:同时注册IdentityUser和ApplicationUser的AddIdentity,导致服务冲突,令牌提供器未正确绑定到ApplicationUser。
  2. 手动构造UserManager缺少必要依赖:实例化时传入的令牌验证器、配置参数等为null,UserManager无法找到默认令牌提供器。
  3. 服务提供器构建时机错误:在添加日志、IHttpContextAccessor等依赖前就构建了ServiceProvider,导致这些服务无法被UserManager使用。
  4. 异步方法调用错误:直接对GeneratePasswordResetTokenAsync调用.ToString(),未等待异步完成,既拿不到正确令牌,也可能引发其他问题。

修正步骤

1. 清理并正确注册Identity服务

移除重复的AddIdentity<IdentityUser, IdentityRole>(),只保留ApplicationUser的Identity注册,确保自动添加默认令牌提供器:

// 移除这行无效注册:services.AddIdentity<IdentityUser, IdentityRole>();
services.AddIdentity<ApplicationUser, IdentityRole>()
       .AddEntityFrameworkStores<ApplicationDbContext>()
       .AddDefaultTokenProviders(); // 自动注册默认令牌提供器

2. 提前添加必要依赖并正确构建ServiceProvider

在构建ServiceProvider前添加日志、DataProtection等核心依赖(令牌生成依赖DataProtection加密):

// 添加必要服务
services.AddLogging();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDataProtection(); // 令牌加密需要此服务

// 最后构建服务提供器
IServiceProvider serviceProvider = services.BuildServiceProvider();

3. 通过ServiceProvider获取UserManager(推荐)

避免手动构造UserManager,直接从DI容器获取,确保所有依赖都正确注入:

var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();

4. 修正异步方法调用

将GeneratePasswordResetTokenAsync的调用改为异步等待,同时更新相关操作的异步方法:

// 方法需改为async
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()
                        {
                            ...
                        };
                 _dbContext.Add(email);
                 await _dbContext.SaveChangesAsync();
             }
         }
     }
}

(可选)如果必须手动构造UserManager

若一定要手动实例化,需传入所有必要依赖并手动注册令牌提供器:

var _applicationContext = new ApplicationDbContext(optionsBuilderApplication.UseSqlServer(connectionStrings.ToString()).Options);
IUserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_applicationContext);

// 从服务容器获取必要依赖
var identityOptions = serviceProvider.GetRequiredService<IOptions<IdentityOptions>>().Value;
var tokenProvider = serviceProvider.GetRequiredService<IUserTwoFactorTokenProvider<ApplicationUser>>();
var logger = serviceProvider.GetRequiredService<ILogger<UserManager<ApplicationUser>>>();
var userValidators = new List<IUserValidator<ApplicationUser>> { new UserValidator<ApplicationUser>() };
var passwordValidators = new List<IPasswordValidator<ApplicationUser>> { new PasswordValidator<ApplicationUser>() };

// 实例化UserManager
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(
    store,
    identityOptions,
    new PasswordHasher<ApplicationUser>(),
    userValidators,
    passwordValidators,
    new UpperInvariantLookupNormalizer(),
    new IdentityErrorDescriber(),
    serviceProvider,
    logger);

// 手动注册默认令牌提供器
userManager.RegisterTokenProvider("Default", tokenProvider);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 05:35:42