如何在IdentityServer4+AspNetCore.Identity中用邮件OTP替代验证器TOTP
替换Skoruba IdentityServer4.Admin默认TOTP为邮件OTP的实现方案
该实现基于ASP.NET Core Identity原生能力扩展,不需要修改Skoruba项目的核心底层代码,可直接通过服务替换完成。
步骤1:实现自定义邮件OTP令牌提供者
新建EmailOtpTokenProvider<TUser>类,继承IUserTwoFactorTokenProvider<TUser>,实现令牌生成、校验逻辑:
public class EmailOtpTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser> where TUser : IdentityUser { private readonly IEmailSender _emailSender; private readonly IUserStore<TUser> _userStore; public EmailOtpTokenProvider(IEmailSender emailSender, IUserStore<TUser> userStore) { _emailSender = emailSender; _userStore = userStore; } // 生成OTP令牌并发送邮件 public async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user) { // 生成6位数字OTP var otp = new Random().Next(100000, 999999).ToString(); // 存储OTP到用户令牌表,设置有效期10分钟 await manager.SetAuthenticationTokenAsync(user, "EmailOtp", $"Otp_{purpose}", otp); await manager.SetAuthenticationTokenAsync(user, "EmailOtp", $"OtpExpiry_{purpose}", DateTime.UtcNow.AddMinutes(10).ToString("o")); // 发送OTP到用户邮箱 await _emailSender.SendEmailAsync(user.Email, "登录验证OTP", $"您的登录验证码为:{otp},10分钟内有效"); return otp; } // 校验OTP令牌 public async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user) { var savedOtp = await manager.GetAuthenticationTokenAsync(user, "EmailOtp", $"Otp_{purpose}"); var expiryStr = await manager.GetAuthenticationTokenAsync(user, "EmailOtp", $"OtpExpiry_{purpose}"); if (string.IsNullOrEmpty(savedOtp) || string.IsNullOrEmpty(expiryStr) || !DateTime.TryParse(expiryStr, out DateTime expiry)) return false; // 校验OTP是否正确且未过期 if (savedOtp == token && expiry > DateTime.UtcNow) { // 校验通过后删除已使用的OTP await manager.RemoveAuthenticationTokenAsync(user, "EmailOtp", $"Otp_{purpose}"); await manager.RemoveAuthenticationTokenAsync(user, "EmailOtp", $"OtpExpiry_{purpose}"); return true; } return false; } public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user) { // 校验用户是否已绑定有效邮箱 return Task.FromResult(!string.IsNullOrEmpty(user.Email) && user.EmailConfirmed); } }
步骤2:替换Identity默认双因素令牌提供者
找到项目启动配置文件(Program.cs/Startup.cs),修改Identity服务注册配置,替换默认的TOTP验证器:
// 原有Identity注册逻辑中新增如下配置 services.AddIdentity<IdentityUser, IdentityRole>(opts => { // 配置默认双因素验证提供者为我们实现的邮件OTP opts.Tokens.DefaultTwoFactorTokenProvider = "EmailOtp"; }) .AddTokenProvider<EmailOtpTokenProvider<IdentityUser>>("EmailOtp") // 保留原有其他配置,如AddEntityFrameworkStores等 .AddEntityFrameworkStores<AdminIdentityDbContext>();
步骤3:调整双因素登录页面逻辑
找到账号登录相关的控制器下的双因素验证逻辑(一般为AccountController.LoginWith2fa),做如下调整:
- 当用户触发双因素验证时,优先调用
_userManager.GenerateTwoFactorTokenAsync(user, "EmailOtp")触发OTP生成和邮件发送 - 原有验证TOTP的逻辑替换为调用
_userManager.VerifyTwoFactorTokenAsync(user, "EmailOtp", code)完成校验
注意事项
- 需要提前确保用户账号已完成邮箱验证,可在注册、个人资料编辑页增加邮箱必填和验证逻辑
- 可自行扩展令牌生成逻辑增加防暴力破解限制,比如同一用户1分钟内只能请求1次OTP
- 替换后原有绑定了Authenticator的用户将无法使用原TOTP方式登录,可根据业务需求做兼容处理或者引导用户重新绑定验证方式
内容的提问来源于stack exchange,提问作者pkExec
相关产品推荐
相关产品推荐

