使用 IdentityServer3 进行双重身份验证 - 记住浏览器

Two factor auth with IdentityServer3 - remember browser

我正在使用 IdentityServer3 + Asp.Net Identity (2.2.1) 实施 2fa。我坚持 2fa 实施。我查看了 "AspNetIdentity_2fa" 样本,这很有帮助。

除了指示浏览器已成功通过身份验证的 cookie 外,我已将所有内容连接起来。我可以在代码确认期间设置cookie,但我无法在PostAuthenticateLocalAsync() 调用中获取cookie 以查看是否采用2fa 路径。

        protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
    {
        if (user.TwoFactorEnabled) // && !TwoFactorCookieSet...
        {
            return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
        }

        return base.PostAuthenticateLocalAsync(user, message);
    }

我相信我在使用部分登录时采用了正确的方法,但我如何检测当前浏览器是否已被批准?

更多细节:/auth/sendcode 是 2fa 的标准 Asp.Net 身份 pages/flow,结合示例中的部分登录逻辑。

好的,我发现OwinEnvironmentService可以注入IdentityServer服务。我可以通过 OwinEnvironmentService 获取 cookie。我很想听听对此解决方案的任何意见(这并不意味着生产就绪,它只是一个概念):

internal class UserService : AspNetIdentityUserService<User, string>
{
    private readonly OwinEnvironmentService _owinEnvironmentService;

    public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
    {
        _owinEnvironmentService = owinEnvironmentService;
        DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
    }

    protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
    {
        if (user.TwoFactorEnabled)
        {
            var twoFactorNeeded = false;
            object httpContext;

            if (_owinEnvironmentService.Environment.TryGetValue("System.Web.HttpContextBase", out httpContext))
            {
                var cookies = (httpContext as HttpContext)?.Request.Cookies;
                if (cookies != null && !cookies.AllKeys.Contains(IdentityConstants.CookieNames.TwoFactorCompleted)) twoFactorNeeded = true;
            }

            if (twoFactorNeeded)
                return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
        }

        return base.PostAuthenticateLocalAsync(user, message);
    }
}

已更新

根据 Brock 的评论,我认为我有更好的解决方案。

// custom User Service
internal class UserService : AspNetIdentityUserService<User, string>
{
    private readonly OwinEnvironmentService _owinEnvironmentService;

    public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
    {
        _owinEnvironmentService = owinEnvironmentService;
        DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
    }

    protected override async Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
    {
        if (user.TwoFactorEnabled)
        {
            var owinContext = new OwinContext(_owinEnvironmentService.Environment);
            var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
            if(result == null) return new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName);
        }

        return await base.PostAuthenticateLocalAsync(user, message);
    }
}

// (in MVC controller) generate the 2FA security code and send it
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
    // ...some code removed for brevity...

    var token = await UserManager.GenerateTwoFactorTokenAsync(userId, model.SelectedProvider);
    var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, model.SelectedProvider, token);
    if (!identityResult.Succeeded) return View("Error");

    return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, model.ReturnUrl, model.RememberMe });
}

// (in MVC controller) verify the code and sign in with 2FA
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
    // ...some code removed for brevity...

    var signInManager = new SignInManager<User, string>(UserManager, Request.GetOwinContext().Authentication);
    if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, model.Provider, model.Code))
    {
        await UserManager.ResetAccessFailedCountAsync(user.Id);
        await signInManager.SignInAsync(user, model.RememberMe, model.RememberBrowser);
        var resumeUrl = await env.GetPartialLoginResumeUrlAsync();
        return Redirect(resumeUrl);
    }
    else
    {
        await UserManager.AccessFailedAsync(user.Id);
        ModelState.AddModelError("", "Invalid code.");
        return View(model);
    }
}

我为记住浏览器要求实现了同样的方法,但是以下语句 return 在我们注销和登录时始终为 null again.so 不会跳过双工厂步骤..

var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);