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

如何结合单因素与双因素认证Cookie实现标准身份验证流程?

解决方案

核心问题分析

你遇到的问题本质是单因素验证通过后生成的Cookie被误判为完整认证状态,Microsoft Identity本身提供了原生的双因素认证(TFA)流程,无需自定义两套Cookie的判断逻辑,只需调整现有认证流程,让框架自动管理临时单因素Cookie和最终认证Cookie的状态。


1. 替换自定义Cookie为框架原生方案

放弃手动维护singlefactorcookie和twofactorcookie,改用Microsoft Identity自带的双因素认证Cookie机制:

  • 单因素验证通过后,框架会生成临时双因素Cookie(默认名称.AspNetCore.Identity.TwoFactorUserId),仅用于标记用户已通过账号密码验证,等待TFA校验,此时User.Identity.IsAuthenticated会返回false。
  • 完成TFA校验后,框架生成最终认证Cookie(默认名称.AspNetCore.Identity.Application),此时User.Identity.IsAuthenticated才会返回true。

如果需要自定义Cookie名称,可在配置中指定:

// Program.cs 配置部分
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

// 配置最终认证Cookie(对应"记住我"的持久化Cookie)
builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = "twofactorcookie";
    options.ExpireTimeSpan = TimeSpan.FromDays(14);
    options.LoginPath = "/Account/Login";
});

// 配置单因素验证后的临时Cookie
builder.Services.Configure<CookieAuthenticationOptions>(IdentityConstants.TwoFactorUserIdScheme, options =>
{
    options.Cookie.Name = "singlefactorcookie";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
    options.LoginPath = "/Account/Login";
});

2. 调整登录流程代码

单因素登录接口(账号密码验证)

[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
    if (!ModelState.IsValid) return View(model);

    // 调用框架方法验证账号密码,自动处理TFA跳转逻辑
    var result = await _signInManager.PasswordSignInAsync(
        model.Email, 
        model.Password, 
        model.RememberMe, 
        lockoutOnFailure: false
    );

    if (result.RequiresTwoFactor)
    {
        // 用户需完成TFA,跳转至验证页面
        return RedirectToAction(nameof(LoginWith2fa), new { model.RememberMe });
    }

    // 其他结果处理(如登录失败、账号锁定等)
    if (!result.Succeeded)
    {
        ModelState.AddModelError(string.Empty, "无效的登录尝试。");
        return View(model);
    }

    return RedirectToAction(nameof(HomeController.Index), "Home");
}

TFA验证接口

[HttpPost]
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model)
{
    var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
    if (user == null) throw new InvalidOperationException("无法加载待验证用户");

    // 验证TFA代码,自动生成最终认证Cookie
    var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(
        model.Code, 
        model.RememberMe, 
        model.RememberMachine
    );

    if (result.Succeeded)
    {
        // 完成完整认证,跳转首页
        return RedirectToAction(nameof(HomeController.Index), "Home");
    }

    ModelState.AddModelError(string.Empty, "无效的验证码。");
    return View(model);
}

3. 修改首页认证检查逻辑

确保只有完成完整双因素认证的用户才能访问首页:

public async Task<IActionResult> Index()
{
    if (User.Identity.IsAuthenticated)
    {
        // 可选:检查用户是否启用了TFA,且当前会话已完成验证
        var user = await _userManager.GetUserAsync(User);
        var isTfaCompleted = await _signInManager.IsTwoFactorClientRememberedAsync(user);
        
        // 或通过认证声明判断是否完成TFA(amr声明记录了认证方式)
        var hasTfaClaim = User.Claims.Any(c => c.Type == "amr" && c.Value == "totp");

        if (isTfaCompleted || hasTfaClaim)
        {
            return View();
        }
        // 未完成TFA,跳转至验证页面
        return RedirectToAction(nameof(AccountController.LoginWith2fa));
    }

    // 未认证,跳转登录页
    return RedirectToAction(nameof(AccountController.Login));
}

方案说明

  • 框架自动管理两个Cookie的生命周期和状态:临时Cookie仅在TFA验证流程中有效,不会触发IsAuthenticated为true;最终Cookie仅在完成TFA后生成,代表完整认证状态。
  • 当用户在单因素验证后打开新标签页,由于只有临时Cookie,User.Identity.IsAuthenticated会返回false,或通过声明检查发现未完成TFA,自动跳转至验证页面,避免未授权访问。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 18:20:33