共用方式為


透過 ASP.NET Identity 使用簡訊和電子郵件進行的雙重要素驗證

作者 Pranav RastogiRick AndersonSuhas Joshi

本教學課程將說明如何使用 SMS 和電子郵件設定雙因素驗證 (2FA) 。

本文是由 Rick Anderson (@RickAndMSFT @RickAndMSFT) 、Pranav Rastogi (@rustd) 、一文和 Suhas Joshi 所撰寫。 NuGet 範例主要是由 Factor Kung 所撰寫。

本主題包含下列項目:

建置身分識別範例

在本節中,您將使用 NuGet 來下載我們將使用的範例。 從安裝並執行Visual Studio Express 2013 for WebVisual Studio 2013開始。 安裝 Visual Studio 2013 Update 2 或更高版本。

注意

警告:您必須安裝 Visual Studio 2013 Update 2 ,才能完成本教學課程。

  1. 建立新的 空白 ASP.NET Web 專案。

  2. 在 [套件管理員主控台] 中,輸入下列命令:

    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples

    在本教學課程中,我們將使用 SendGrid 來傳送電子郵件和 TwilioASPSMS 進行簡訊簡訊。 套件 Identity.Samples 會安裝我們將使用的程式碼。

  3. 專案設定為使用 SSL

  4. 選用:請遵循我的Email確認教學課程中的指示來連結 SendGrid,然後執行應用程式並註冊電子郵件帳戶。

  5. 選: 從範例中移除示範電子郵件連結確認碼 (ViewBag.Link 帳戶控制器中的程式碼。 DisplayEmail 請參閱 和 ForgotPasswordConfirmation 動作方法和 razor 檢視) 。

  6. 選: 從 Manage 和 Account 控制器中移除程式 ViewBag.Status 代碼,以及從 Views\Account\VerifyCode.cshtmlViews\Manage\VerifyPhoneNumber.cshtml razor 檢視中移除程式碼。 或者,您可以保留 ViewBag.Status 顯示以測試此應用程式在本機的運作方式,而不需要連結及傳送電子郵件和簡訊。

注意

警告:如果您變更此範例中的任何安全性設定,生產應用程式將需要進行安全性稽核,以明確呼叫所做的變更。

設定雙因素驗證的 SMS

本教學課程提供使用 Twilio 或 ASPSMS 的指示,但您可以使用任何其他 SMS 提供者。

  1. 使用 SMS 提供者建立使用者帳戶

    建立 TwilioASPSMS 帳戶。

  2. 安裝其他套件或新增服務參考

    Twilio:
    在 [套件管理器主控台] 中,輸入下列命令:
    Install-Package Twilio

    ASPSMS:
    必須新增下列服務參考:

    新增服務參考視窗的影像

    位址:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    命名空間:
    ASPSMSX2

  3. 找出 SMS 提供者使用者認證

    Twilio:
    從 Twilio 帳戶的 [ 儀表板 ] 索引標籤中,複製 [帳戶 SID ] 和 [ 驗證] 權杖

    ASPSMS:
    從您的帳戶設定中,流覽至 Userkey ,並將它與您的自我定義 密碼一起複製。

    我們稍後會將這些值儲存在 變數 SMSAccountIdentificationSMSAccountPassword 中。

  4. 指定 SenderID / Originator

    Twilio:
    從 [ 號碼] 索引 標籤中,複製您的 Twilio 電話號碼。

    ASPSMS:
    在 [ 解除鎖定原始程式 ] 功能表中,解除鎖定一或多個 [原始程式],或選擇英數位元來源器 (所有網路) 不支援。

    我們稍後會將此值儲存在 變數 SMSAccountFrom 中。

  5. 將 SMS 提供者認證傳輸至應用程式

    讓認證和寄件者電話號碼可供應用程式使用:

    public static class Keys
    {
       public static string SMSAccountIdentification = "My Idenfitication";
       public static string SMSAccountPassword = "My Password";
       public static string SMSAccountFrom = "+15555551234";
    }
    

    警告

    安全性 - 永不將敏感性資料儲存在您的原始程式碼中。 帳戶和認證會新增至上述程式碼,以便讓範例保持簡單。 請參閱 Jon Atten ASP.NET MVC:將私人設定保留在原始檔控制外

  6. 將資料傳輸至 SMS 提供者的實作

    SmsServiceApp_Start\IdentityConfig.cs檔案中設定 類別。

    視使用的 SMS 提供者而定,會啟用 TwilioASPSMS 區段:

    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Twilio Begin
            // var Twilio = new TwilioRestClient(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword);
            // var result = Twilio.SendMessage(
            //   Keys.SMSAccountFrom,
            //   message.Destination, message.Body
            // );
            // Status is one of Queued, Sending, Sent, Failed or null if the number is not valid
            // Trace.TraceInformation(result.Status);
            // Twilio doesn't currently have an async API, so return success.
            // return Task.FromResult(0);
            // Twilio End
    
            // ASPSMS Begin 
            // var soapSms = new WebApplication1.ASPSMSX2.ASPSMSX2SoapClient("ASPSMSX2Soap");
            // soapSms.SendSimpleTextSMS(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword,
            //   message.Destination,
            //   Keys.SMSAccountFrom,
            //   message.Body);
            // soapSms.Close();
            // return Task.FromResult(0);
            // ASPSMS End
        }
    }
    
  7. 執行應用程式,並使用您先前註冊的帳戶登入。

  8. 按一下您的 [使用者識別碼],這會啟動 Index 控制器中的 Manage 動作方法。

    登入應用程式的已註冊帳戶影像

  9. 按一下 [新增]。

    新增電話號碼連結的影像

  10. 幾秒鐘後,您會收到含有驗證碼的簡訊。 輸入它,然後按 [提交]。

    顯示手機驗證碼專案的影像

  11. [管理] 檢視會顯示已新增您的電話號碼。

    顯示電話號碼的管理檢視視窗影像

檢查程式碼

// GET: /Account/Index
public async Task<ActionResult> Index(ManageMessageId? message)
{
    ViewBag.StatusMessage =
        message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
        : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
        : message == ManageMessageId.SetTwoFactorSuccess ? "Your two factor provider has been set."
        : message == ManageMessageId.Error ? "An error has occurred."
        : message == ManageMessageId.AddPhoneSuccess ? "The phone number was added."
        : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
        : "";

    var model = new IndexViewModel
    {
        HasPassword = HasPassword(),
        PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId()),
        TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId()),
        Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()),
        BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(User.Identity.GetUserId())
    };
    return View(model);
}

控制器中的 Manage 動作方法會 Index 根據您先前的動作來設定狀態訊息,並提供變更本機密碼或新增本機帳戶的連結。 此方法 Index 也會顯示狀態或您的 2FA 電話號碼、外部登入、已啟用 2FA,並記住此瀏覽器的 2FA 方法, (稍後說明) 。 按一下標題列中 (電子郵件) 的使用者識別碼不會傳遞訊息。 按一下 [電話號碼:移除 ] 連結會以查詢字串的形式傳遞 Message=RemovePhoneSuccess

https://localhost:44300/Manage?Message=RemovePhoneSuccess

[已移除電話號碼的影像]

AddPhoneNumber動作方法會顯示對話方塊,以輸入可接收 SMS 訊息的電話號碼。

// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
   return View();
}

新增電話號碼動作對話方塊的影像

按一下 [ 傳送驗證碼 ] 按鈕,將電話號碼張貼至 HTTP POST AddPhoneNumber 動作方法。

// POST: /Account/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // Generate the token 
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
                               User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        // Send token
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

方法 GenerateChangePhoneNumberTokenAsync 會產生將在 SMS 訊息中設定的安全性權杖。 如果已設定 SMS 服務,權杖會以字串「您的安全性代碼為 < 權杖 > 」傳送。 方法 SmsService.SendAsync 會以非同步方式呼叫,然後將應用程式重新導向至 VerifyPhoneNumber 動作方法 (,其中會顯示下列對話方塊) ,您可以在其中輸入驗證碼。

驗證電話號碼動作方法對話方塊的影像

輸入程式碼並按一下 [提交] 之後,程式碼就會張貼至 HTTP POST VerifyPhoneNumber 動作方法。

// POST: /Account/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
    if (result.Succeeded)
    {
        var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
        if (user != null)
        {
            await SignInAsync(user, isPersistent: false);
        }
        return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
    }
    // If we got this far, something failed, redisplay form
    ModelState.AddModelError("", "Failed to verify phone");
    return View(model);
}

方法 ChangePhoneNumberAsync 會檢查張貼的安全性代碼。 如果程式碼正確,電話號碼會新增至 PhoneNumber 資料表的 AspNetUsers 欄位。 如果該呼叫成功,則會 SignInAsync 呼叫 方法:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   // Clear the temporary cookies used for external and two factor sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

參數 isPersistent 會設定驗證會話是否會跨多個要求保存。

當您變更安全性設定檔時,會產生新的安全性戳記,並儲存在 SecurityStampAspNetUsers 資料表的 欄位中。 請注意, SecurityStamp 欄位與安全性 Cookie 不同。 安全性 Cookie 不會儲存在 AspNetUsers 資料表中 (或 Identity DB) 的任何其他位置。 安全性 Cookie 權杖是使用 DPAPI 自我簽署,並使用 和 到期時間資訊建立 UserId, SecurityStamp

Cookie 中介軟體會檢查每個要求的 Cookie。 類別 SecurityStampValidator 中的 Startup 方法會叫用 DB,並定期檢查安全性戳記,如 所 validateInterval 指定。 除非您變更安全性設定檔,否則只會在我們的範例) 中每隔 30 分鐘 (一次。 已選擇 30 分鐘的間隔,以將資料庫的車程降到最低。

SignInAsync對安全性設定檔進行任何變更時,必須呼叫 方法。 當安全性設定檔變更時,資料庫會更新 SecurityStamp 欄位,而不需要呼叫 SignInAsync 方法,您 只會 在下次 OWIN 管線叫用資料庫時 validateInterval () 為止。 您可以將 方法變更 SignInAsync 為立即傳回,並將 Cookie validateInterval 屬性從 30 分鐘設定為 5 秒,藉此進行測試:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   return;

   // Clear any partial cookies from external or two factor partial sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}
public void ConfigureAuth(IAppBuilder app) {
    // Configure the db context, user manager and role manager to use a single instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a 
    // third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider {
            // Enables the application to validate the security stamp when the user logs in.
            // This is a security feature which is used when you change a password or add 
            // an external login to your account.  
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //validateInterval: TimeSpan.FromMinutes(30),
                validateInterval: TimeSpan.FromSeconds(5),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        }
    });
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

透過上述程式碼變更,您可以變更安全性設定檔 (例如,藉由變更 已啟用 Two Factor enabled) 的狀態,並在方法失敗時 SecurityStampValidator.OnValidateIdentity 于 5 秒內登出。 移除 方法中的 SignInAsync 傳回行,進行另一個安全性設定檔變更,而且您將不會登出。方法 SignInAsync 會產生新的安全性 Cookie。

啟用雙因素驗證

在範例應用程式中,您必須使用 UI 來啟用雙因素驗證 (2FA) 。 若要啟用 2FA,請按一下導覽列中 (電子郵件別名) 的使用者識別碼。啟用雙因素驗證的 U I 影像
按一下 [啟用 2FA]。 按一下顯示雙因素驗證啟用連結的使用者識別碼之後的影像 登出,然後重新登入。 如果您已啟用電子郵件 (請參閱 上一 個教學課程) ,您可以選取 2FA 的 SMS 或電子郵件。 顯示驗證傳送選項的影像 [驗證代碼] 頁面隨即顯示,您可以從 SMS 或電子郵件) 輸入代碼 (。 驗證字碼頁的影像 按一下 [ 記住此瀏覽器 ] 核取方塊,即可免除您使用 2FA 登入該電腦和瀏覽器的需求。 啟用 2FA 並按一下 [記住此瀏覽器 ] 會提供您強式 2FA 保護,防止嘗試存取您的帳戶的惡意使用者,只要他們無法存取您的電腦即可。 您可以在您定期使用的任何私人電腦上執行此動作。 藉由設定 [記住此瀏覽器],您會從未定期使用的電腦取得 2FA 的新增安全性,而且您不需要在自己的電腦上通過 2FA 時獲得便利性。

如何註冊雙因素驗證提供者

當您建立新的 MVC 專案時, IdentityConfig.cs 檔案包含下列程式碼來註冊雙因素驗證提供者:

public static ApplicationUserManager Create(
   IdentityFactoryOptions<ApplicationUserManager> options, 
   IOwinContext context) 
{
    var manager = new ApplicationUserManager(
       new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    // Configure validation logic for usernames
    manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
    };
    // Configure validation logic for passwords
    manager.PasswordValidator = new PasswordValidator
    {
        RequiredLength = 6,
        RequireNonLetterOrDigit = true,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
    };
    // Register two factor authentication providers. This application uses Phone and Emails as a 
    // step of receiving a code for verifying the user
    // You can write your own provider and plug it in here.
    manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
    {
        MessageFormat = "Your security code is: {0}"
    });
    manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
    {
        Subject = "Security Code",
        BodyFormat = "Your security code is: {0}"
    });
    manager.EmailService = new EmailService();
    manager.SmsService = new SmsService();

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>
           (dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

新增 2FA 的電話號碼

控制器 AddPhoneNumber 中的 Manage 動作方法會產生安全性權杖,並將它傳送至您提供的電話號碼。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Generate the token and send it
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
       User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

傳送權杖之後,它會重新導向至 VerifyPhoneNumber 動作方法,您可以在其中輸入程式碼來註冊 2FA 的 SMS。 在您驗證電話號碼之前,不會使用 SMS 2FA。

啟用 2FA

EnableTFA動作方法會啟用 2FA:

// POST: /Manage/EnableTFA
[HttpPost]
public async Task<ActionResult> EnableTFA()
{
    await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Index", "Manage");
}

請注意,必須呼叫 , SignInAsync 因為 enable 2FA 是安全性設定檔的變更。 啟用 2FA 時,使用者必須使用 2FA 登入,並使用他們在範例) 中註冊的 2FA 方法 (SMS 和電子郵件。

您可以新增更多 2FA 提供者,例如 QR 代碼產生器,也可以自行撰寫。

注意

2FA 代碼是使用 以時間為基礎的單次密碼演算法 所產生,而且代碼有效期為六分鐘。 如果您輸入程式碼需要六分鐘以上的時間,您會收到不正確程式碼錯誤訊息。

結合社交和本機登入帳戶

您可以按一下電子郵件連結來合併本機和社交帳戶。 在下列順序中,「 RickAndMSFT@gmail.com 」 會先建立為本機登入,但您可以先將帳戶建立為社交記錄,然後新增本機登入。

選取電子郵件連結的影像

按一下 [ 管理] 連結。 請注意與此帳戶相關聯的 0 個外部 (社交登入) 。

顯示下一頁並選取 [管理] 的影像

按一下另一個登入服務的連結,並接受應用程式要求。 這兩個帳戶已合併,您將能夠使用任一帳戶登入。 您可能會想要讓使用者新增本機帳戶,以防其社交記錄驗證服務已關閉,或者他們可能失去其社交帳戶的存取權。

在下圖中,Tom 是 (中的社交記錄 ,您可以從外部登入看到:1 顯示在頁面上) 。

顯示外部登入和挑選密碼位置的影像

按一下 [挑選密碼 ] 可讓您新增與相同帳戶相關聯的本機登入。

挑選密碼頁面的影像

帳戶從暴力密碼破解攻擊鎖定

您可以藉由啟用使用者鎖定來保護應用程式上的帳戶免于字典攻擊。 方法中的 ApplicationUserManager Create 下列程式碼會設定鎖定:

// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;

上述程式碼只允許鎖定雙因素驗證。 雖然您可以在帳戶控制器的 方法中 Login 變更 shouldLockout 為 true 來啟用登入鎖定,但建議您不要針對登入啟用鎖定,因為它會使帳戶容易受到DOS登入攻擊。 在範例程式碼中,會針對在 ApplicationDbInitializer Seed 方法中建立的系統管理員帳戶停用鎖定:

public static void InitializeIdentityForEF(ApplicationDbContext db)
{
    var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string roleName = "Admin";

    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null)
    {
        role = new IdentityRole(roleName);
        var roleresult = roleManager.Create(role);
    }

    var user = userManager.FindByName(name);
    if (user == null)
    {
        user = new ApplicationUser { UserName = name, Email = name };
        var result = userManager.Create(user, GetSecurePassword());
        result = userManager.SetLockoutEnabled(user.Id, false);
    }

    // Add user admin to Role Admin if not already added
    var rolesForUser = userManager.GetRoles(user.Id);
    if (!rolesForUser.Contains(role.Name))
    {
        var result = userManager.AddToRole(user.Id, role.Name);
    }
}

要求使用者擁有已驗證的電子郵件帳戶

下列程式碼需要使用者擁有已驗證的電子郵件帳戶,才能登入:

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

   // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
       if (!await UserManager.IsEmailConfirmedAsync(user.Id))
       {
          ViewBag.errorMessage = "You must have a confirmed email to log on.";
          return View("Error");
       }         
    }
    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, 
       model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

SignInManager 如何檢查 2FA 需求

本機登入和社交記錄都會檢查是否已啟用 2FA。 如果已啟用 2FA, SignInManager 則登入方法會 SignInStatus.RequiresVerification 傳回 ,而使用者將會重新導向至 SendCode 動作方法,其中必須輸入程式碼才能依序完成登入。 如果使用者已在使用者本機 Cookie 上設定 RememberMe,則會 SignInManager 傳回 SignInStatus.Success ,而且他們不需要通過 2FA。

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

   // Require the user to have a confirmed email before they can log on.
    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
       if (!await UserManager.IsEmailConfirmedAsync(user.Id))
       {
          ViewBag.errorMessage = "You must have a confirmed email to log on.";
          return View("Error");
       }         
    }
    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, 
       model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        return RedirectToAction("Login");
    }

    // Sign in the user with this external login provider if the user already has a login
    var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            // If the user does not have an account, then prompt the user to create an account
            ViewBag.ReturnUrl = returnUrl;
            ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
            return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
    }
}

下列程式碼顯示 SendCode 動作方法。 SelectListItem會建立,並啟用使用者的所有 2FA 方法。 SelectListItem會傳遞至DropDownListFor協助程式,讓使用者選取 2FA 方法 (通常是電子郵件和簡訊) 。

public async Task<ActionResult> SendCode(string returnUrl)
{
    var userId = await SignInManager.GetVerifiedUserIdAsync();
    if (userId == null)
    {
        return View("Error");
    }
    var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
    var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
    return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}

一旦使用者張貼 2FA 方法之後, HTTP POST SendCode 會呼叫動作方法、 SignInManager 傳送 2FA 程式碼,並將使用者重新導向至 VerifyCode 動作方法,讓他們可以在其中輸入程式碼以完成登入。

//
// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // Generate the token and send it
    if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
    {
        return View("Error");
    }
    return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}

2FA 鎖定

雖然您可以在登入密碼嘗試失敗時設定帳戶鎖定,但這種方法會使您的登入容易遭受 DOS 鎖定。 建議您只搭配 2FA 使用帳戶鎖定。 ApplicationUserManager建立 時,範例程式碼會將 2FA 鎖定和 MaxFailedAccessAttemptsBeforeLockout 設定為五。 一旦使用者透過本機帳戶或社交帳戶) 登入 (,就會儲存 2FA 的每個失敗嘗試,如果達到最大嘗試,使用者就會鎖定五分鐘, (您可以使用) 來設定鎖定時間 DefaultAccountLockoutTimeSpan

其他資源