使用 ASP.NET Identity (C#) 進行帳戶確認和密碼復原

執行本教學課程之前,您應該先完成 使用登入、電子郵件確認和密碼重設建立安全 ASP.NET MVC 5 Web 應用程式。 本教學課程包含更多詳細資料,並說明如何設定本機帳戶確認的電子郵件,並允許使用者在 ASP.NET 身分識別中重設忘記的密碼。

本機使用者帳戶需要使用者建立帳戶的密碼,且該密碼會安全地儲存在 Web 應用程式中 () 。 ASP.NET 身分識別也支援社交帳戶,不需要使用者為應用程式建立密碼。 社交帳戶 會使用協力廠商 (,例如 Google、Twitter、Facebook 或 Microsoft) 來驗證使用者。 本主題包含下列項目:

新使用者註冊其電子郵件別名,這會建立本機帳戶。

帳戶註冊視窗的影像

選取 [註冊] 按鈕會將包含驗證權杖的確認電子郵件傳送至其電子郵件地址。

顯示電子郵件傳送確認的影像

使用者會收到一封電子郵件,其中包含其帳戶的確認權杖。

確認權杖的影像

選取連結會確認帳戶。

確認電子郵件地址的影像

密碼復原/重設

忘記密碼的本機使用者可以將安全性權杖傳送至其電子郵件帳戶,讓他們重設其密碼。

忘記密碼重設視窗的影像

使用者很快就會收到一封電子郵件,其中包含可讓他們重設其密碼的連結。

顯示重設密碼電子郵件的影像
選取連結會帶他們前往 [重設] 頁面。

顯示使用者密碼重設視窗的影像

選取 [ 重設] 按鈕將會確認密碼已重設。

顯示密碼重設確認的影像

建立 ASP.NET Web 應用程式

從安裝和執行 Visual Studio 2017開始。

  1. 建立新的 ASP.NET Web 專案,然後選取 MVC 範本。 Web Form也支援 ASP.NET 身分識別,因此您可以遵循 Web 表單應用程式中的類似步驟。

  2. 將驗證變更為 [個別使用者帳戶]。

  3. 執行應用程式,選取 [ 註冊 ] 連結並註冊使用者。 此時,電子郵件的唯一驗證是 [ EmailAddress] 屬性。

  4. 在 [伺服器總管] 中,流覽至 [資料連線\DefaultConnection\Tables\AspNetUsers],以滑鼠右鍵按一下並選取 [ 開啟資料表定義]。

    下圖顯示 AspNetUsers 架構:

    顯示 A p Net Users 架構的影像

  5. 以滑鼠右鍵按一下 [AspNetUsers ] 資料表,然後選取 [ 顯示資料表資料]。

    顯示資料表資料的影像

    此時尚未確認電子郵件。

ASP.NET 身分識別的預設資料存放區是 Entity Framework,但您可以將它設定為使用其他資料存放區,以及新增其他欄位。 See Additional Resources section at the end of this tutorial.

當應用程式啟動時,會呼叫OWIN 啟動類別 ( Startup.cs ) ,並在App_Start\Startup.Auth.cs中叫 ConfigureAuth 用 方法,這會設定 OWIN 管線並初始化 ASP.NET Identity。 檢查 ConfigureAuth 方法。 每個呼叫都會 CreatePerOwinContext 註冊回呼 (儲存在) 中 OwinContext ,每個要求都會呼叫一次,以建立指定類型的實例。 您可以在每個類型的建構函式和 Create 方法中設定中斷點 () ApplicationDbContext, ApplicationUserManager ,並確認在每個要求上呼叫它們。 和 ApplicationUserManagerApplicationDbContext 實例會儲存在 OWIN 內容中,可在整個應用程式中存取。 ASP.NET 身分識別會透過 Cookie 中介軟體連結至 OWIN 管線。 如需詳細資訊,請參閱 ASP.NET Identity 中 UserManager 類別的每個要求存留期管理

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

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

根據程式碼中的批註, UseCookieAuthentication 方法支援 Cookie 驗證。 SecurityStamp欄位和相關聯的程式碼會為您的應用程式提供額外的安全性層級,當您變更密碼時,系統會將您登出您登入的瀏覽器。 方法 SecurityStampValidator.OnValidateIdentity 可讓應用程式在使用者登入時驗證安全性權杖,當您變更密碼或使用外部登入時會使用此權杖。 這是為了確保使用舊密碼產生的任何權杖 (Cookie) 都會失效。 在範例專案中,如果您變更使用者密碼,則會為使用者產生新的權杖,任何先前的權杖都會失效,並 SecurityStamp 更新欄位。

身分識別系統可讓您設定應用程式,因此當使用者的安全性設定檔變更 (例如,當使用者變更其密碼或變更相關聯的登入 (,例如從 Facebook、Google、Microsoft 帳戶等) 時,使用者就會登出所有瀏覽器實例。 例如,下圖顯示 單一登入範例 應用程式,可讓使用者透過選取一個按鈕來登出所有瀏覽器實例 (。在此情況下,IE、Firefox 和 Chrome) 。 或者,此範例可讓您只登出特定的瀏覽器實例。

顯示單一登出範例應用程式視窗的影像

單一登入範例應用程式示範 ASP.NET 身分識別如何讓您重新產生安全性權杖。 這是為了確保使用舊密碼產生的任何權杖 (Cookie) 都會失效。 這項功能可為您的應用程式提供額外的安全性層;當您變更密碼時,將會登出您已登入此應用程式的位置。

App_Start\IdentityConfig.cs檔案包含 ApplicationUserManagerEmailServiceSmsService 類別。 EmailServiceSmsService 類別各實作 IIdentityMessageService 介面,因此您在每個類別中都有一般方法來設定電子郵件和簡訊。 雖然本教學課程只會示範如何透過 SendGrid新增電子郵件通知,但您可以使用 SMTP 和其他機制來傳送電子郵件。

類別 Startup 也包含用於新增社交登入 (Facebook、Twitter 等) 的樣板,如需詳細資訊,請參閱我的 教學課程 MVC 5 App with Facebook、Twitter、LinkedIn 和 Google OAuth2 登入

檢查 類別 ApplicationUserManager ,其中包含使用者身分識別資訊,並設定下列功能:

  • 密碼強度需求。
  • 使用者鎖定 (嘗試和時間) 。
  • 雙因素驗證 (2FA) 。 我將在另一個教學課程中討論 2FA 和 SMS。
  • 連結電子郵件和簡訊服務。 (我將會在另一個教學課程中討論 SMS) 。

類別 ApplicationUserManager 衍生自泛型 UserManager<ApplicationUser> 類別。 ApplicationUser 衍生自 IdentityUserIdentityUser 衍生自泛型 IdentityUser 類別:

//     Default EntityFramework IUser implementation
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
   where TLogin : IdentityUserLogin<TKey>
   where TRole : IdentityUserRole<TKey>
   where TClaim : IdentityUserClaim<TKey>
{
   public IdentityUser()
   {
      Claims = new List<TClaim>();
      Roles = new List<TRole>();
      Logins = new List<TLogin>();
   }

   ///     User ID (Primary Key)
   public virtual TKey Id { get; set; }

   public virtual string Email { get; set; }
   public virtual bool EmailConfirmed { get; set; }

   public virtual string PasswordHash { get; set; }

   ///     A random value that should change whenever a users credentials have changed (password changed, login removed)
   public virtual string SecurityStamp { get; set; }

   public virtual string PhoneNumber { get; set; }
   public virtual bool PhoneNumberConfirmed { get; set; }

   public virtual bool TwoFactorEnabled { get; set; }

   ///     DateTime in UTC when lockout ends, any time in the past is considered not locked out.
   public virtual DateTime? LockoutEndDateUtc { get; set; }

   public virtual bool LockoutEnabled { get; set; }

   ///     Used to record failures for the purposes of lockout
   public virtual int AccessFailedCount { get; set; }
   
   ///     Navigation property for user roles
   public virtual ICollection<TRole> Roles { get; private set; }

   ///     Navigation property for user claims
   public virtual ICollection<TClaim> Claims { get; private set; }

   ///     Navigation property for user logins
   public virtual ICollection<TLogin> Logins { get; private set; }
   
   public virtual string UserName { get; set; }
}

上述屬性與資料表中的 AspNetUsers 屬性一致,如上所示。

上的 IUser 泛型引數可讓您針對主鍵使用不同類型的衍生類別。 請參閱 ChangePK 範例,其中示範如何將主鍵從字串變更為 int 或 GUID。

ApplicationUser

ApplicationUserpublic class ApplicationUserManager : UserManager<ApplicationUser> () 在Models\IdentityModels.cs中定義為:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in 
       //   CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, 
    DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

上述醒目提示的程式碼會產生 ClaimsIdentity。 ASP.NET 身分識別和 OWIN Cookie 驗證是以宣告為基礎,因此架構需要應用程式為使用者產生 ClaimsIdentityClaimsIdentity 具有使用者所有宣告的相關資訊,例如使用者的名稱、年齡和使用者所屬的角色。 您也可以在此階段為使用者新增更多宣告。

OWIN AuthenticationManager.SignIn 方法會傳入 ClaimsIdentity 並登入使用者:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties(){
       IsPersistent = isPersistent }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

MVC 5 應用程式與 Facebook、Twitter、LinkedIn和 Google OAuth2 登入會顯示 如何將其他屬性新增至 ApplicationUser 類別。

Email確認

建議您確認新使用者註冊的電子郵件,以確認他們未模擬其他人 (也就是說,他們尚未向其他人的電子郵件註冊) 。 假設您有討論論壇,建議您防止 "bob@example.com" 註冊為 "joe@contoso.com" 。 如果沒有電子郵件確認, "joe@contoso.com" 可能會從您的應用程式收到不必要的電子郵件。 假設 Bob 不小心註冊為 "bib@example.com" ,但未注意到,他將無法使用密碼復原,因為應用程式沒有正確的電子郵件。 Email確認僅提供來自 Bot 的有限保護,且不提供來自已決定垃圾郵件者的保護,他們有許多可用來註冊的工作電子郵件別名。在下列範例中,使用者必須選取他們註冊的電子郵件 (帳戶上收到的確認連結,才能變更其密碼。) 您可以將此工作流程套用至其他案例,例如傳送連結來確認和重設系統管理員所建立之新帳戶的密碼。 當使用者變更其設定檔等等時,傳送電子郵件給使用者。 您通常想要防止新使用者在電子郵件、簡訊或其他機制確認之前,將任何資料張貼到您的網站。

建置更完整的範例

在本節中,您將使用 NuGet 來下載我們將使用的更完整範例。

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

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

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

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

  3. 專案設定為使用 SSL

  4. 執行應用程式、選取 [ 註冊 ] 連結並張貼註冊表單,以測試本機帳戶建立。

  5. 選取示範電子郵件連結,以模擬電子郵件確認。

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

警告

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

檢查 App_Start\IdentityConfig.cs 中的程式碼

此範例示範如何建立帳戶,並將其新增至管理員角色。 您應該將範例中的電子郵件取代為您將用於系統管理員帳戶的電子郵件。 現在,在 方法中 Seed ,以程式設計方式建立系統管理員帳戶最簡單的方式。 我們希望未來有一個工具,可讓您建立和管理使用者和角色。 範例程式碼可讓您建立和管理使用者和角色,但您必須先有系統管理員帳戶才能執行角色和使用者系統管理員頁面。 在此範例中,系統會在植入資料庫時建立系統管理員帳戶。

變更密碼,並將名稱變更為您可以接收電子郵件通知的帳戶。

警告

安全性 - 永不將敏感性資料儲存在您的原始程式碼中。

如先前所述, app.CreatePerOwinContext 啟動類別中的 呼叫會將回呼新增至 Create 應用程式 DB 內容、使用者管理員和角色管理員類別的 方法。 OWIN 管線 Create 會針對每個要求呼叫這些類別上的 方法,並儲存每個類別的內容。 帳戶控制器會從包含 OWIN 內容) 的 HTTP 內容 (公開使用者管理員:

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? 
    HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

當使用者註冊本機帳戶時,會 HTTP Post Register 呼叫 方法:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action(
               "ConfirmEmail", "Account", 
               new { userId = user.Id, code = code }, 
               protocol: Request.Url.Scheme);

            await UserManager.SendEmailAsync(user.Id, 
               "Confirm your account", 
               "Please confirm your account by clicking this link: <a href=\"" 
                                               + callbackUrl + "\">link</a>");
            // ViewBag.Link = callbackUrl;   // Used only for initial demo.
            return View("DisplayEmail");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

上述程式碼會使用模型資料,使用輸入的電子郵件和密碼來建立新的使用者帳戶。 如果電子郵件別名位於資料存放區中,帳戶建立會失敗,並再次顯示表單。 方法 GenerateEmailConfirmationTokenAsync 會建立安全的確認權杖,並將它儲存在 ASP.NET 身分識別資料存放區中。 Url.Action方法會建立包含 UserId 和 確認權杖的連結。 然後,此連結會以電子郵件傳送給使用者,使用者可以選取其電子郵件應用程式中的連結以確認其帳戶。

設定電子郵件確認

移至 SendGrid 註冊頁面並註冊免費帳戶。 新增類似下列的程式碼以設定 SendGrid:

public class EmailService : IIdentityMessageService
{
   public Task SendAsync(IdentityMessage message)
   {
      return configSendGridasync(message);
   }

   private Task configSendGridasync(IdentityMessage message)
   {
      var myMessage = new SendGridMessage();
      myMessage.AddTo(message.Destination);
      myMessage.From = new System.Net.Mail.MailAddress(
                          "Joe@contoso.com", "Joe S.");
      myMessage.Subject = message.Subject;
      myMessage.Text = message.Body;
      myMessage.Html = message.Body;

      var credentials = new NetworkCredential(
                 ConfigurationManager.AppSettings["mailAccount"],
                 ConfigurationManager.AppSettings["mailPassword"]
                 );

      // Create a Web transport for sending email.
      var transportWeb = new Web(credentials);

      // Send the email.
      if (transportWeb != null)
      {
         return transportWeb.DeliverAsync(myMessage);
      }
      else
      {
         return Task.FromResult(0);
      }
   }
}

注意

Email用戶端經常只接受文字訊息, (沒有 HTML) 。 您應該以文字和 HTML 提供訊息。 在上述 SendGrid 範例中,這會使用 myMessage.Text 上述的 和 myMessage.Html 程式碼來完成。

下列程式碼示範如何使用 MailMessage 類別傳送電子郵件,其中 message.Body 只會傳回連結。

void sendMail(Message message)
{
#region formatter
   string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
   string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";

   html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
#endregion

   MailMessage msg = new MailMessage();
   msg.From = new MailAddress("joe@contoso.com");
   msg.To.Add(new MailAddress(message.Destination));
   msg.Subject = message.Subject;
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

   SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", Convert.ToInt32(587));
   System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("joe@contoso.com", "XXXXXX");
   smtpClient.Credentials = credentials;
   smtpClient.EnableSsl = true;
   smtpClient.Send(msg);
}

警告

安全性 - 永不將敏感性資料儲存在您的原始程式碼中。 帳戶和認證會儲存在 appSetting 中。 在 Azure 上,您可以在 Azure 入口網站的 [ 設定 ] 索引標籤上安全地儲存這些值。 請參閱 將密碼和其他敏感性資料部署至 ASP.NET 和 Azure 的最佳做法

輸入您的 SendGrid 認證、執行應用程式、向電子郵件別名註冊,即可選取電子郵件中的確認連結。 若要瞭解如何使用 您的 Outlook.com 電子郵件帳戶執行這項操作,請參閱 John Atten 的 C# SMTP 設定,以 Outlook.Com SMTP 主機 及其ASP.NET 身分識別 2.0:設定帳戶驗證和Two-Factor授權 文章。

一旦使用者選取 [ 註冊 ] 按鈕,就會將包含驗證權杖的確認電子郵件傳送至其電子郵件地址。

電子郵件傳送確認視窗的影像

使用者會收到一封電子郵件,其中包含其帳戶的確認權杖。

收到電子郵件的影像

檢查程式碼

下列程式碼顯示 POST ForgotPassword 方法。

public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            // Don't reveal that the user does not exist or is not confirmed
            return View("ForgotPasswordConfirmation");
        }

        var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", 
    new { UserId = user.Id, code = code }, protocol: Request.Url.Scheme);
        await UserManager.SendEmailAsync(user.Id, "Reset Password", 
    "Please reset your password by clicking here: <a href=\"" + callbackUrl + "\">link</a>");        
        return View("ForgotPasswordConfirmation");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

如果尚未確認使用者電子郵件,此方法會以無訊息方式失敗。 如果針對不正確電子郵件地址張貼錯誤,惡意使用者可以使用該資訊來尋找有效的 userId (電子郵件別名) 攻擊。

下列程式碼會顯示 ConfirmEmail 在帳戶控制器中呼叫的方法,當使用者在傳送給他們的電子郵件中選取確認連結時呼叫:

public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
    if (userId == null || code == null)
    {
        return View("Error");
    }
    var result = await UserManager.ConfirmEmailAsync(userId, code);
    if (result.Succeeded)
    {
        return View("ConfirmEmail");
    }
    AddErrors(result);
    return View();
}

使用忘記的密碼權杖之後,就會失效。 App_Start\IdentityConfig.cs檔案中方法 (下列程式碼變更 Create ,) 將權杖設定為在 3 小時內到期。

if (dataProtectionProvider != null)
 {
    manager.UserTokenProvider =
       new DataProtectorTokenProvider<ApplicationUser>
          (dataProtectionProvider.Create("ASP.NET Identity"))
          {                    
             TokenLifespan = TimeSpan.FromHours(3)
          };
 }

使用上述程式碼時,忘記的密碼和電子郵件確認權杖將在 3 小時內到期。 預設值 TokenLifespan 為一天。

下列程式碼顯示電子郵件確認方法:

// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
   if (userId == null || code == null)
   {
      return View("Error");
   }
   IdentityResult result;
   try
   {
      result = await UserManager.ConfirmEmailAsync(userId, code);
   }
   catch (InvalidOperationException ioe)
   {
      // ConfirmEmailAsync throws when the userId is not found.
      ViewBag.errorMessage = ioe.Message;
      return View("Error");
   }

   if (result.Succeeded)
   {
      return View();
   }

   // If we got this far, something failed.
   AddErrors(result);
   ViewBag.errorMessage = "ConfirmEmail failed";
   return View("Error");
}

若要讓您的應用程式更安全,ASP.NET 身分識別支援Two-Factor驗證 (2FA) 。 請參閱 ASP.NET Identity 2.0:設定 John Atten 的帳戶驗證和Two-Factor授權 。 雖然您可以在登入密碼嘗試失敗時設定帳戶鎖定,但這種方法會使您的登入容易遭受 DOS 鎖定。 建議您只搭配 2FA 使用帳戶鎖定。

其他資源