ASP.NET Core 中使用短信的双因素身份验证

作者:Rick AndersonSwiss-Devs

警告

双因素身份验证 (2FA) 验证器应用使用基于时间的一次性密码算法 (TOTP),是行业推荐用于 2FA 的方法。 使用 TOTP 的 2FA 方法首选短信 2FA。 有关详细信息,请参阅适用于 ASP.NET Core 2.0 及更高版本的在 ASP.NET Core 中为 TOTP 验证器应用启用 QR 码生成

本教程演示如何使用短信设置双因素身份验证 (2FA)。 提供了适用于 twilioASPSMS 的说明,但你可以使用任何其他 SMS 提供商。 建议在开始学习本教程之前先完成帐户确认和密码恢复

查看或下载示例代码下载方法

创建新的 ASP.NET Core 项目

使用单独用户帐户创建名为 Web2FA 的新 ASP.NET Core Web 应用。 按照在 ASP.NET Core 中强制执行 HTTPS 中的说明进行操作,设置并要求使用 HTTPS。

创建短信帐户

创建一个短信帐户,例如 twilioASPSMS。 记录身份验证凭据(对于 twilio:accountSid 和 authToken,对于 ASPSMS:用户密钥和密码)。

找到短信提供商凭据

Twilio:

在 Twilio 帐户的“仪表板”选项卡中,复制“帐户 SID”和“身份验证令牌”。

ASPSMS:

从帐户设置中,导航到“用户密钥”,并将其与“密码”一起复制。

稍后,我们会将这些值与机密管理器工具存储在密钥 SMSAccountIdentificationSMSAccountPassword 中。

指定 SenderID/发信方

Twilio:在“号码”选项卡中,复制 Twilio 电话号码。

ASPSMS:在“解锁发信方”菜单中,解锁一个或多个发信方,或者选择字母数字发信方(并非所有网络都支持此选项)。

稍后,我们会将这些值与机密管理器工具存储在密钥 SMSAccountFrom 中。

提供短信服务的凭据

我们将使用选项模式来访问用户帐户和密钥设置。

  • 创建一个类来获取安全短信密钥。 对于本示例,在 Services/SMSoptions.cs 文件中创建了 SMSoptions 类。
namespace Web2FA.Services
{
    public class SMSoptions
    {
        public string SMSAccountIdentification { get; set; }
        public string SMSAccountPassword { get; set; }
        public string SMSAccountFrom { get; set; }
    }
}

使用机密管理器工具设置 SMSAccountIdentificationSMSAccountPasswordSMSAccountFrom。 例如:

C:/Web2FA/src/WebApp1>dotnet user-secrets set SMSAccountIdentification 12345
info: Successfully saved SMSAccountIdentification = 12345 to the secret store.
  • 添加短信提供商的 NuGet 包。 在“包管理器控制台”(PMC) 中,运行:

Twilio:

Install-Package Twilio

ASPSMS:

Install-Package ASPSMS

  • Services/MessageServices.cs 文件中添加代码以启用短信。 使用 Twilio 或 ASPSMS 部分的内容:

Twilio:

using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

namespace Web2FA.Services
{
    // This class is used by the application to send Email and SMS
    // when you turn on two-factor authentication in ASP.NET Identity.
    // For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
    public class AuthMessageSender : IEmailSender, ISmsSender
    {
        public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
        {
            Options = optionsAccessor.Value;
        }

        public SMSoptions Options { get; }  // set only via Secret Manager

        public Task SendEmailAsync(string email, string subject, string message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }

        public Task SendSmsAsync(string number, string message)
        {
            // Plug in your SMS service here to send a text message.
            // Your Account SID from twilio.com/console
            var accountSid = Options.SMSAccountIdentification;
            // Your Auth Token from twilio.com/console
            var authToken = Options.SMSAccountPassword;

            TwilioClient.Init(accountSid, authToken);

            return MessageResource.CreateAsync(
              to: new PhoneNumber(number),
              from: new PhoneNumber(Options.SMSAccountFrom),
              body: message);
        }
    }
}

ASPSMS:

using Microsoft.Extensions.Options;
using System.Threading.Tasks;

namespace Web2FA.Services
{
    // This class is used by the application to send Email and SMS
    // when you turn on two-factor authentication in ASP.NET Identity.
    // For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
    public class AuthMessageSender : IEmailSender, ISmsSender
    {
        public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
        {
            Options = optionsAccessor.Value;
        }

        public SMSoptions Options { get; }  // set only via Secret Manager

        public Task SendEmailAsync(string email, string subject, string message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }

        public Task SendSmsAsync(string number, string message)
        {
            ASPSMS.SMS SMSSender = new ASPSMS.SMS();

            SMSSender.Userkey = Options.SMSAccountIdentification;
            SMSSender.Password = Options.SMSAccountPassword;
            SMSSender.Originator = Options.SMSAccountFrom;

            SMSSender.AddRecipient(number);
            SMSSender.MessageData = message;

            SMSSender.SendTextSMS();

            return Task.FromResult(0);
        }
    }
}

配置启动以使用 SMSoptions

SMSoptions 添加到 Startup.csConfigureServices 方法的服务容器:

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
    services.Configure<SMSoptions>(Configuration);
}

启用双因素身份验证

打开 Views/Manage/Index.cshtmlRazor 视图文件并删除注释字符(这样不会注释掉任何标记)。

使用双因素身份验证进行登录

  • 运行应用并注册新用户

Web application Register view open in Microsoft Edge

  • 点击用户名,这会将激活“管理控制器”中的 Index 操作方法。 然后点击电话号码“添加”链接。

Manage view - tap the

  • 添加将接收验证码的电话号码,然后点击“发送验证码”。

Add Phone Number page

  • 你将收到一条包含验证码的短信。 输入验证码,然后点击“提交”

Verify Phone Number page

如果未收到短信,请参阅 twilio 日志页面。

  • “管理”视图显示已成功添加电话号码。

Manage view - phone number added successfully

  • 点击“启用”以启用双因素身份验证。

Manage view - enable two-factor authentication

测试双因素身份验证

  • 注销。

  • 登录。

  • 用户帐户已启用双重身份验证,因此必须提供第二个身份验证因素。 在本教程中,你已启用电话验证。 内置模板还允许将电子邮件设置为第二个因素。 可以设置其他第二种身份验证因素,例如 QR 码。 点击“提交”。

Send Verification Code view

  • 输入 SMS 消息中获得的代码。

  • 单击“记住此浏览器”复选框后,在使用同一设备和浏览器时,无需使用2FA 进行登录。 如果启用 2FA 并单击“记住此浏览器”,将获得强大的 2FA 防护,可以防范恶意用户尝试访问帐户(只要他们无权访问你的设备)。 可以在经常使用的任何专用设备上进行此操作。 通过设置“记住此浏览器”,你可以从不经常使用的设备中获得 2FA 的增强的安全性,并且无需在自己的设备上进行 2FA。

Verify view

锁定帐户以防止暴力攻击

建议将帐户锁定与 2FA 配合使用。 用户通过本地帐户或社交帐户登录后,系统会存储 2FA 的每次失败尝试。 如果达到最大访问失败尝试次数,用户将被锁定(默认时长:5分钟,在 5 次失败的访问尝试后锁定)。 身份验证成功后,将重置失败访问尝试计数并重置时钟。 可以通过 MaxFailedAccessAttemptsDefaultLockoutTimeSpan 设置最大失败访问尝试次数和锁定时间。 以下内容将帐户配置为 10 次失败的访问尝试后锁定 10 分钟:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    services.Configure<IdentityOptions>(options =>
    {
        options.Lockout.MaxFailedAccessAttempts = 10;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
    });

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
    services.Configure<SMSoptions>(Configuration);
}

确认 PasswordSignInAsynclockoutOnFailure设置为 true

var result = await _signInManager.PasswordSignInAsync(
                 Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);