在aspnet核心中使用电子邮件的双因素认证

ASP中电子邮件的双因素认证.NET Core

Introduction

我们最近有一个客户,他想建立一个web应用程序,以便他们的客户几乎实时地访问信息. 客户端目前通过PDF报告推送客户感兴趣的数据,但使用新的web应用程序, 客户端的客户可以在任何需要的时候提取实时信息. 在讨论这个问题时,首先出现的问题之一是如何保护web应用程序. 大多数现代的面向客户机的web应用程序都有某种形式的双因素身份验证(2FA). 这为传统的用户名和密码提供了额外的安全级别. 这最初是通过SMS或电子邮件实现的,但通过Microsoft authenticator或谷歌authenticator等身份验证应用程序,使用基于时间的一次性密码算法(TOTP)实现了更安全的实现.

Issues with Authenticator Apps

而使用TOTP提供了高级别的安全性, 有一些问题,您的客户端基础使用身份验证应用程序. 当用户丢失安装认证应用程序的设备时,会发生什么? 他们运气不好,不能访问你的web应用. 但他们可以恢复备份代码,当他们把你的web应用程序添加到你说的新设备上的身份验证应用程序时生成的. 有多大比例的用户会花时间保存这些备份代码? I’m guessing it’s pretty small. 此外,如果web应用程序的用户基础主要或部分是老年人怎么办? 这些用户中有多少人拥有智能手机,或者他们是否能够知道如何设置和使用身份验证应用程序? 如果用户想要访问web应用程序,却没有自己的设备,那该怎么办?

我写这篇文章不是为了说明为什么你不应该使用身份验证应用程序. 我个人使用它们,它们为我提供了高度的信心,我的数据受到了保护. However, 当你为客户端创建一个web应用程序时,你需要对你的用户基础将如何访问你的应用程序做出明智的决定. 

2FA with Email

For a variety of reasons, 我们决定在这个网络应用程序中不使用认证程序,而是使用电子邮件. We discussed SMS, 它的实现方式和电子邮件很相似, 但是短信服务需要额外的费用, 它还要求用户在访问web应用程序时带着他们的手机.  所以我们只能在2FA中使用电子邮件. 当用户使用其用户名和密码登录时, 应用程序将向他们发送一封带有验证码的电子邮件. 验证码输入正确, 他们将能够访问web应用程序.

请注意,微软和许多其他公司不再建议使用SMS或电子邮件作为第二个因素,因为这种实现存在大量的攻击载体. 由您来考虑要保护哪些数据以及您的用户群将如何访问应用程序.

Implementation

Microsoft ASP.NET Identity内置了对2FA的支持. However, after .NET Core 1.出于安全考虑,官方取消了对使用短信或电子邮件的2FA的支持. 通过少量的自定义编码,仍然可以使用ASP实现.NET Core Identity. 注意本文中的代码示例是 .NET Core 3.1.

如果你还没有,你需要添加ASP.NET Core Identity到您的Visual Studio解决方案中,或者选择在创建新项目时添加它的选项. For more information on ASP.NET Core Identity, see my blog post. 作为开始工作的先决条件,你应该完成以下工作.

  1. Identity setup configured.
  2. Identity Razor pages scaffolded.
  3. 首先通过实体框架代码或TSQL脚本创建SQL Server标识表.

您要做的第一件事是查看登录页面, 默认位于/身份/帐户/登录的身份区域. Note that the Identity SignInManager returns an Identity SignInResult 它有一个我们可以利用的名为“RequiresTwoFactor”的属性. 该属性的值来自ApplicationUser表中的“TwoFactorEnabled”列. 在创建用户帐户时,您需要确保将该列设置为true,或者在数据库模式中将其默认值设置为true. In the code below, 我们可以看到“RequiresTwoFactor”是否被重定向到一个页面“LoginVerifyCode”(注意,默认情况下Identity将这个页面构建为“LoginWith2fa”).

// attempt to sign the user in
                   var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.密码,true, lockoutOnFailure: true);
                    if (result.IsLockedOut)
                    {
                        _identityLogger.Log
                            .ForContext("UserName", Input.UserName)
                            .ForContext("RequestUri", Request.GetUri())
                            .Warning($"User {Input.UserName} account locked out.");
                        return RedirectToPage("./Lockout");
                    }
                    else if (result.IsNotAllowed)
                    {
                        //用户不允许登录,因为电子邮件地址没有被确认
                        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                        return Page();
                    }
                    else if (result.RequiresTwoFactor)
                        //重定向到电子邮件验证码页
                        返回RedirectToPage("LoginVerifyCode", new {id = Input.UserName, returnnurl = returnnurl});
                    else if (result.Succeeded)
                    {

现在用户已经被重定向到验证代码页面, 在Get中我们要做的第一件事是通过调用UserManager类中的FindByNameAsync来获取对ApplicationUser的引用.  这确保了我们从登录页面传递的用户名确实是一个有效的用户,而且我们需要对该用户的引用来生成2FA令牌.

The next step is to call GenerateTwoFactorTokenAsync 在UserManager类中,它将为试图登录的用户生成唯一的6位代码.  The method takes two parameters, 对ApplicationUser和令牌(SMS或电子邮件)提供者的引用.

获得令牌后,您将在发送给用户的电子邮件中包含该令牌.  我创建了一个自定义类,通过我为这个应用程序选择的电子邮件实现发送验证码.  (类实现的细节超出了本文的范围, 但它是一个简单的SmtpClient实现).

//确认用户电子邮件是有效的
                var user = await _userManager.FindByNameAsync(id);
                if (user == null)
                    返回RedirectToPage("/Error/StatusCode", new {code = "403"});

                Input = new InputModel
                {
                    Email = user.Email,
                    UserName = user.UserName,
                    ReturnUrl = returnUrl
                };

                // generate the 2fa token
                var token = await _userManager.GenerateTwoFactorTokenAsync(用户、“电子邮件”);

                //通过电子邮件发送2fa令牌给用户
                var emailResult = await _userManagerService . var emailResult = await _userManagerService.SendVerificationCodeEmail(user.Email, token, _emailerService, _emailLogger);
                if (!string.IsNullOrEmpty(emailResult))
                {
                    AssignStatusMessage(“发送验证码邮件时出错.", StatusMessageTypeEnum.Warning);
                    AssignStatusMessageTempData();
                }

因此,现在用户应该已经收到带有2FA令牌的电子邮件.  他们输入验证码,然后点击验证页面上的提交按钮, 我们需要在Post中验证该代码.  To do so, we leverage the TwoFactorSignInAsync method in the SignInManager class.  This method takes four parameters.

  1. The Provider which sent the token. In this case it’s Email.
  2. 用户通过电子邮件接收并输入的令牌.
  3. 一个标志,指示cookie中的标志在浏览器关闭后是否仍然存在. 我建议将其设置为false(尤其是在共享计算机上).
  4. 一个指示当前浏览器是否应该被记住的标志. More on that later.

TwoFactorSignInAsync方法返回一个SingInResult,我们可以使用它来确定令牌是否有效.  如果返回“Succeeded”,我们就可以让用户继续进入应用程序.  如果不是,则显示一条错误消息,并询问用户是否希望再次发送令牌.

if (ModelState.IsValid)
                {
                    try
                    {
                        var result = await _signInManager.TwoFactorSignInAsync(“电子邮件”,输入.EmailCode, false, Input.RememberComputer);
                        if (result.Succeeded)
                        {
                            _identityLogger.Log
                                .ForContext("UserName", Input.UserName)
                                .ForContext("RequestUri", Request.GetUri())
                                .Information($"User {Input.用户名}已经验证了他们的2FA电子邮件代码.");

                            return Redirect(Input.ReturnUrl ?? "/");
                        }
                    }
                    catch (Exception ex)
                    {

为用户提供了额外的便利, 您可以选择在用户的浏览器上记住2FA登录. 这当然是一个值得思考的安全问题. 而对于用户来说,不必每次登录都输入2FA代码就很方便了, 在某些情况下,您永远不会允许这样做(例如共享公共计算机). As you can see in the code above, 我们将输入控件的值传递给用户,让用户决定是否在浏览器上被记住. 如果该控件的值作为true传递给rememberClient属性的TwoFactorSignInAsync方法, 2FA将通过一个cookie被持久化.

Conclusion

2FA应该是您开发的任何面向公共的web应用程序的需求. 您确实可以选择如何实现2FA,所以在做决定时要考虑安全性需求和用户基础.

Further Reading

基于ASP的短信和电子邮件双因素认证.NET Identity
ASP中基于SMS的双因素认证.NET Core
Multi-factor authentication in ASP.NET Core

How Can We Help You?

我们是为web、移动和桌面应用程序开发定制解决方案的专家. 从安全性和可伸缩性到性能和用户体验,我们已经为您介绍了这些内容. 今天和我们谈谈,看看我们能做些什么!

John Hadzima
John HadzimaCore Contributor

John Hadzima是Marathon Consulting的解决方案架构师和团队领导,也是微软认证的解决方案开发人员. 他在Hampton Roads做了20多年的开发人员,从事各种语言和数据库的开发工作,偶尔也会开发手机应用. 约翰喜欢冲浪、骑公路自行车、打高尔夫球,他的两个儿子使他忙得不可开交.

Let's Talk About Your Project.

我们是一家提供全方位IT和数字营销服务的公司. 我们相信,成功的项目是与我们的客户合作和透明的结果. 您是否正在为您的网站或应用程序寻找更好的用户体验? 需要有经验的数据库架构师或业务分析师? Let’s talk!

X