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

未使用ASP.NET Core Identity的.NET Core项目添加TOTP MFA的实现方案

无ASP.NET Core Identity 环境下 .NET 6.0 API 集成TOTP多因素认证方案

TOTP是基于RFC 6238标准的公开算法,本身和ASP.NET Core Identity没有绑定关系。官方文档中的MFA示例与Identity强耦合,只是因为微软为Identity封装了开箱即用的扩展方法,你完全可以在自定义认证体系中独立实现,不需要引入任何Identity相关依赖,原有JWT认证、角色校验逻辑可以完全保留,具体实现步骤如下:

前置依赖准备

  • 不需要安装任何ASP.NET Core Identity相关包,直接引入轻量TOTP实现库Google.Authenticator即可,该库兼容Google Authenticator、Microsoft Authenticator等所有符合标准的TOTP客户端应用。

数据库结构调整

在你现有的自定义Users表中新增3个字段即可:

  • TwoFactorEnabled:bit类型,标记当前用户是否已开启MFA
  • TwoFactorSecret:nvarchar(128)类型,存储用户专属TOTP密钥(注意该字段必须加密存储,禁止明文落库)
  • TwoFactorRecoveryCodes:nvarchar(max)类型,存储哈希后的一次性恢复码,存储逻辑和用户密码一致,禁止明文存储

核心逻辑实现

1. MFA开启流程

用户完成密码校验、处于已登录状态时,可请求开启MFA:

  1. 使用System.Security.Cryptography.RandomNumberGenerator生成16字节以上的密码学安全随机数,编码为Base32格式作为用户专属TOTP密钥
  2. 按照TOTP标准格式生成认证地址:otpauth://totp/[你的系统名称]:[用户名]?secret=[生成的密钥]&issuer=[你的系统名称],将该地址和手动录入用的明文密钥返回给前端,由前端生成二维码供用户用认证App扫描
  3. 此时不要直接标记用户MFA为开启状态,需要等用户提交认证App上显示的6位验证码,校验通过后再将加密后的密钥存入TwoFactorSecret字段,标记TwoFactorEnabled = true
  4. 同时生成5-10个随机字符串作为恢复码,哈希后存入TwoFactorRecoveryCodes字段,将明文恢复码返回给用户妥善保存,用于认证App不可用时的应急登录

核心代码参考:

using Google.Authenticator;

// 生成MFA配置信息
var tfa = new TwoFactorAuthenticator();
var setupResult = tfa.GenerateSetupCode(
    issuer: "你的业务系统名称",
    accountTitle: user.Username,
    accountSecretKey: userSecretKey, // 前面生成的Base32密钥
    qrPixelsPerModule: 3,
    generateQrCode: false // 建议由前端用qrcode类库根据返回的ProvisioningUrl生成二维码,减少后端图片生成开销
);
// 返回 setupResult.ManualEntryKey(手动录入密钥)、setupResult.ProvisioningUrl(二维码内容)给前端

// 校验用户提交的TOTP验证码
var isCodeValid = tfa.ValidateTwoFactorPIN(
    accountSecretKey: storedDecryptedSecret, // 数据库取出后解密的密钥
    twoFactorCode: userSubmitCode,
    timeTolerance: TimeSpan.FromMinutes(1) // 允许前后1分钟的时间偏移,兼容客户端时间误差
);

2. 登录流程改造

原有用户名密码校验通过后直接颁发JWT的逻辑,调整为两阶段校验:

  1. 第一阶段校验用户名+密码,密码错误直接返回认证失败结果;密码校验通过后判断用户MFA状态:
    • 若用户未开启MFA:直接颁发完整权限的正式JWT,和原有逻辑一致,携带角色等所有业务Claims
    • 若用户已开启MFA:仅颁发有效期5分钟的临时JWT,该JWT只携带一个特殊Claim:"mfa_required": true,不携带角色等权限Claims,仅允许访问MFA校验接口
  2. 第二阶段MFA校验:用户拿到临时JWT后,提交认证App显示的6位验证码或者恢复码,请求MFA校验接口:
    • 校验临时JWT合法,取出对应用户信息,优先校验TOTP验证码是否有效;如果TOTP校验不通过,再校验提交内容是否为未使用的有效恢复码(恢复码校验通过后立即标记为已用,不可重复使用)
    • 任意一种校验通过后,为用户颁发完整权限的正式JWT,流程结束

3. 授权策略调整

原有JWT认证配置不需要修改,仅需新增授权策略区分临时JWT和正式JWT即可:

builder.Services.AddAuthorization(options =>
{
    // 默认授权策略:要求已认证且已完成MFA校验(无mfa_required标记)
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .RequireAssertion(ctx => !ctx.User.HasClaim(c => c.Type == "mfa_required"))
        .Build();
    // MFA步骤专用策略:仅允许携带临时MFA校验标识的令牌访问
    options.AddPolicy("MfaStepRequired", policy =>
    {
        policy.RequireAuthenticatedUser()
              .RequireClaim("mfa_required", "true");
    });
});
  • 所有普通业务接口、角色授权接口保持原有[Authorize]、[Authorize(Roles = "Administrator")]特性不变,会自动拦截未完成MFA的临时令牌
  • MFA校验接口标记[Authorize(Policy = "MfaStepRequired")],仅允许临时令牌访问

4. MFA关闭流程

用户完成密码+TOTP验证码二次校验后,清空TwoFactorSecret、TwoFactorRecoveryCodes字段值,将TwoFactorEnabled设为false即可。

安全注意事项

  • TOTP密钥、恢复码均为高敏感数据,必须加密存储,禁止明文落库
  • TOTP校验时间窗口不要设置过大,最大不超过前后1分钟,避免暴力破解风险
  • 记录每个用户最近一次TOTP校验成功的时间戳,同一时间窗口的验证码校验通过后立即作废,防止重放攻击
  • 临时JWT有效期不得超过10分钟,且仅开放MFA校验接口的访问权限,不得放行其他业务接口
  • 密钥生成必须使用密码学安全随机数生成器,禁止使用Random、Guid等非安全随机方式生成密钥

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:48:30