以使用者註冊、電子郵件確認和密碼重設建立安全的 ASP.NET Web Forms 應用程式 (C#)
作者 :Erik Reitan
本教學課程說明如何使用 ASP.NET 身分識別成員資格系統,建置具有使用者註冊、電子郵件確認和密碼重設的 ASP.NET Web Forms應用程式。 本教學課程是以 Rick Anderson 的 MVC 教學課程為基礎。
簡介
本教學課程會引導您使用 Visual Studio 建立 ASP.NET Web Forms應用程式,並 ASP.NET 4.5 建立具有使用者註冊、電子郵件確認和密碼重設的安全Web Form應用程式。
教學課程工作和資訊:
建立 ASP.NET Web Forms應用程式
注意
警告:您必須安裝Visual Studio 2013 Update 3或更高版本,才能完成本教學課程。
建立新的專案 (檔案- >新增專案) ,然後從 [新增專案] 對話方塊中選取[ASP.NET Web 應用程式] 範本和最新的.NET Framework版本。
從 [新增 ASP.NET 專案] 對話方塊中,選取Web Form範本。 將預設驗證保留為 個別使用者帳戶。 如果您想要在 Azure 中裝載應用程式,請保留 [ 雲端中的主機 ] 核取方塊。
然後按一下 [ 確定 ] 以建立新的專案。
為專案啟用安全通訊端層 (SSL) 。 請遵循使用 Web Form 教學課程系列之 消費者入門啟用PROJECT 的 SSL一節中可用的步驟。
執行應用程式,按一下 [註冊] 連結並註冊新的使用者。 此時,電子郵件的唯一驗證是以 [EmailAddress] 屬性為基礎,以確保電子郵件地址格式正確。 您將修改程式碼以新增電子郵件確認。 關閉瀏覽器視窗。
在 Visual Studio 的[伺服器總管] ([檢視] - >[伺服器總管]) 中,流覽至[資料連線\DefaultConnection\Tables\AspNetUsers],以滑鼠右鍵按一下並選取 [開啟資料表定義]。
下圖顯示
AspNetUsers
資料表架構:在 [伺服器總管] 中,以滑鼠右鍵按一下 [AspNetUsers] 資料表,然後選取 [ 顯示資料表資料]。
此時尚未確認已註冊使用者的電子郵件。按一下資料列,然後選取 [刪除] 以刪除使用者。 您將在下一個步驟中再次新增此電子郵件,並將確認訊息傳送至電子郵件地址。
Email確認
最佳做法是在註冊新使用者期間確認電子郵件,以確認他們不會模擬其他人 (也就是說,他們尚未向其他人的電子郵件註冊) 。 假設您有一個討論論壇,您會想要防止 "bob@cpandl.com"
註冊為 "joe@contoso.com"
。 如果沒有電子郵件確認, "joe@contoso.com"
可能會從您的應用程式收到不必要的電子郵件。 假設 Bob 不小心註冊為 "bib@cpandl.com"
且未注意到,他無法使用密碼復原,因為應用程式沒有正確的電子郵件。 Email確認僅提供來自 Bot 的有限保護,且不會提供來自判定垃圾郵件者的保護。
您通常想要防止新使用者在電子郵件、簡訊或其他機制確認之前,將任何資料張貼到您的網站。 在下列各節中,我們將啟用電子郵件確認並修改程式碼,以防止新註冊的使用者登入,直到確認電子郵件為止。 您將在本教學課程中使用電子郵件服務 SendGrid。
連結 SendGrid
SendGrid 已在撰寫本教學課程之後變更其 API。 如需目前的 SendGrid 指示,請參閱 SendGrid 或 啟用帳戶確認和密碼復原。
雖然本教學課程只會示範如何透過 SendGrid新增電子郵件通知,但您可以使用 SMTP 和其他機制傳送電子郵件 (請參閱 其他資源) 。
在 Visual Studio 中,開啟套件管理員主控台 (Tools - >NuGet Package Manger - >Package Manager Console) ,然後輸入下列命令:
Install-Package SendGrid
移至 Azure SendGrid 註冊頁面 並註冊免費的 SendGrid 帳戶。 您也可以直接在 SendGrid 的網站上註冊免費的 SendGrid帳戶。
從方案總管開啟App_Start資料夾中的IdentityConfig.cs檔案,並將下列以黃色醒目提示的程式碼新增至 類別,
EmailService
以設定SendGrid:public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { await configSendGridasync(message); } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync(IdentityMessage message) { var myMessage = new SendGridMessage(); myMessage.AddTo(message.Destination); myMessage.From = new System.Net.Mail.MailAddress( "Royce@contoso.com", "Royce Sellars (Contoso Admin)"); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential( ConfigurationManager.AppSettings["emailServiceUserName"], ConfigurationManager.AppSettings["emailServicePassword"] ); // Create a Web transport for sending email. var transportWeb = new Web(credentials); // Send the email. if (transportWeb != null) { await transportWeb.DeliverAsync(myMessage); } else { Trace.TraceError("Failed to create Web transport."); await Task.FromResult(0); } } }
此外,將下列
using
語句新增至 IdentityConfig.cs 檔案的開頭:using SendGrid; using System.Net; using System.Configuration; using System.Diagnostics;
若要讓此範例保持簡單,您會將電子郵件服務帳戶值儲存在
appSettings
web.config 檔案的 區段中。 將黃色反白顯示的下列 XML 新增至專案根目錄的 web.config 檔案:</connectionStrings> <appSettings> <add key="emailServiceUserName" value="[EmailServiceAccountUserName]" /> <add key="emailServicePassword" value="[EmailServiceAccountPassword]" /> </appSettings> <system.web>
警告
安全性 - 永不將敏感性資料儲存在原始程式碼中。 在此範例中,帳戶和認證會儲存在Web.config檔案的appSetting區段中。 在 Azure 上,您可以在Azure 入口網站的 [設定] 索引標籤上安全地儲存這些值。 如需相關資訊,請參閱 Rick Anderson 的主題,標題為將 密碼和其他敏感性資料部署至 ASP.NET 和 Azure 的最佳做法。
新增電子郵件服務值,以反映您的 SendGrid 驗證值 (使用者名稱和密碼) ,以便您成功從您的應用程式傳送電子郵件。 請務必使用 SendGrid 帳戶名稱,而不是您提供 SendGrid 的電子郵件地址。
啟用Email確認
若要啟用電子郵件確認,您將使用下列步驟來修改註冊碼。
在 [帳戶] 資料夾中,開啟 Register.aspx.cs 程式碼後置並更新
CreateUser_Click
方法來啟用下列醒目提示的變更:protected void CreateUser_Click(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); IdentityHelper.SignIn(manager, user, isPersistent: false); IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); } else { ErrorMessage.Text = result.Errors.FirstOrDefault(); } }
在方案總管中,以滑鼠右鍵按一下Default.aspx,然後選取 [設定為起始頁]。
按 F5 執行應用程式。顯示頁面之後,按一下 [ 註冊] 連結以顯示 [註冊] 頁面。
輸入您的電子郵件和密碼,然後按一下 [ 註冊 ] 按鈕,透過 SendGrid 傳送電子郵件訊息。
專案和程式碼的目前狀態可讓使用者在完成註冊表單後登入,即使他們尚未確認其帳戶也一樣。請檢查您的電子郵件帳戶,然後按一下連結以確認您的電子郵件。
提交註冊表單之後,您將會登入。
登入前需要Email確認
雖然您已確認電子郵件帳戶,但此時您不需要按一下驗證電子郵件中包含的連結,才能完全登入。 在下一節中,您將修改要求新使用者有已確認電子郵件的程式碼,才能登入 (經過驗證) 。
在Visual Studio 方案總管中,使用下列醒目提示的變更,更新
CreateUser_Click
[Accounts]資料夾中所包含Register.aspx.cs程式碼後置中的事件:protected void CreateUser_Click(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); if (user.EmailConfirmed) { IdentityHelper.SignIn(manager, user, isPersistent: false); IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); } else { ErrorMessage.Text = "An email has been sent to your account. Please view the email and confirm your account to complete the registration process."; } } else { ErrorMessage.Text = result.Errors.FirstOrDefault(); } }
LogIn
使用下列醒目提示的變更,更新Login.aspx.cs程式碼後置中的 方法:protected void LogIn(object sender, EventArgs e) { if (IsValid) { // Validate the user password var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>(); // Require the user to have a confirmed email before they can log on. var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { FailureText.Text = "Invalid login attempt. You must have a confirmed email account."; ErrorMessage.Visible = true; } else { // This doen't count login failures towards account lockout // To enable password failures to trigger lockout, change to shouldLockout: true var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false); switch (result) { case SignInStatus.Success: IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); break; case SignInStatus.LockedOut: Response.Redirect("/Account/Lockout"); break; case SignInStatus.RequiresVerification: Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", Request.QueryString["ReturnUrl"], RememberMe.Checked), true); break; case SignInStatus.Failure: default: FailureText.Text = "Invalid login attempt"; ErrorMessage.Visible = true; break; } } } } }
執行應用程式
既然您已實作程式碼來檢查使用者的電子郵件地址是否已確認,您可以同時檢查 [註冊 ] 和 [ 登入 ] 頁面上的功能。
- 刪除 AspNetUsers 資料表中包含您想要測試的電子郵件別名的任何帳戶。
- 執行應用程式 (F5) ,並確認您必須先確認電子郵件地址,才能註冊為使用者。
- 透過剛傳送的電子郵件確認您的新帳戶之前,請先嘗試使用新帳戶登入。
您會看到您無法登入,而且您必須有確認的電子郵件帳戶。 - 確認電子郵件地址之後,請登入應用程式。
密碼復原和重設
在 Visual Studio 中,從
Forgot
[Account] 資料夾中包含的 Forget.aspx.cs程式碼後置方法中移除批註字元,讓方法如下所示:protected void Forgot(object sender, EventArgs e) { if (IsValid) { // Validate the user's email address var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); ApplicationUser user = manager.FindByName(Email.Text); if (user == null || !manager.IsEmailConfirmed(user.Id)) { FailureText.Text = "The user either does not exist or is not confirmed."; ErrorMessage.Visible = true; return; } // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 // Send email with the code and the redirect to reset password page string code = manager.GeneratePasswordResetToken(user.Id); string callbackUrl = IdentityHelper.GetResetPasswordRedirectUrl(code, Request); manager.SendEmail(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>."); loginForm.Visible = false; DisplayEmail.Visible = true; } }
開啟 Login.aspx 頁面。 取代 loginForm 區段結尾附近的標記,如下所示:
<%@ Page Title="Log in" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebForms002.Account.Login" Async="true" %> <%@ Register Src="~/Account/OpenAuthProviders.ascx" TagPrefix="uc" TagName="OpenAuthProviders" %> <asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent"> <h2><%: Title %>.</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> <div class="form-horizontal"> <h4>Use a local account to log in.</h4> <hr /> <asp:PlaceHolder runat="server" ID="ErrorMessage" Visible="false"> <p class="text-danger"> <asp:Literal runat="server" ID="FailureText" /> </p> </asp:PlaceHolder> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Email" CssClass="col-md-2 control-label">Email</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Email" CssClass="form-control" TextMode="Email" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Email" CssClass="text-danger" ErrorMessage="The email field is required." /> </div> </div> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Password" CssClass="col-md-2 control-label">Password</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Password" TextMode="Password" CssClass="form-control" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Password" CssClass="text-danger" ErrorMessage="The password field is required." /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> <asp:CheckBox runat="server" ID="RememberMe" /> <asp:Label runat="server" AssociatedControlID="RememberMe">Remember me?</asp:Label> </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <asp:Button runat="server" OnClick="LogIn" Text="Log in" CssClass="btn btn-default" /> </div> </div> </div> <p> <asp:HyperLink runat="server" ID="RegisterHyperLink" ViewStateMode="Disabled">Register as a new user</asp:HyperLink> </p> <p> <%-- Enable this once you have account confirmation enabled for password reset functionality --%> <asp:HyperLink runat="server" ID="ForgotPasswordHyperLink" ViewStateMode="Disabled">Forgot your password?</asp:HyperLink> </p> </section> </div> <div class="col-md-4"> <section id="socialLoginForm"> <uc:OpenAuthProviders runat="server" ID="OpenAuthLogin" /> </section> </div> </div> </asp:Content>
開啟 Login.aspx.cs 程式碼後置,並從事件處理常式取消批註以黃色
Page_Load
醒目提示的下列程式程式碼:protected void Page_Load(object sender, EventArgs e) { RegisterHyperLink.NavigateUrl = "Register"; // Enable this once you have account confirmation enabled for password reset functionality ForgotPasswordHyperLink.NavigateUrl = "Forgot"; OpenAuthLogin.ReturnUrl = Request.QueryString["ReturnUrl"]; var returnUrl = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]); if (!String.IsNullOrEmpty(returnUrl)) { RegisterHyperLink.NavigateUrl += "?ReturnUrl=" + returnUrl; } }
按 F5 執行應用程式。顯示頁面之後,按一下 [ 登入] 連結。
按一下 [忘記密碼嗎?] 連結以顯示 [忘記密碼] 頁面。
輸入您的電子郵件地址,然後按一下 [ 提交 ] 按鈕,將電子郵件傳送至您的位址,以允許您重設密碼。
請檢查您的電子郵件帳戶,然後按一下連結以顯示 [ 重設密碼 ] 頁面。在 [ 重設密碼] 頁面上,輸入您的電子郵件、密碼和確認的密碼。 然後按 [ 重設 ] 按鈕。
當您成功重設密碼時,將會顯示 [ 密碼變更 ] 頁面。 現在,您可以使用新的密碼登入。
重新傳送Email確認連結
一旦使用者建立新的本機帳戶,他們就會以電子郵件傳送確認連結,讓他們能夠登入。 如果使用者不小心刪除確認電子郵件,或電子郵件永遠不會送達,他們將需要再次傳送確認連結。 下列程式碼變更示範如何啟用此功能。
在 Visual Studio 中,開啟 Login.aspx.cs 程式碼後置,並在事件處理常式之後
LogIn
新增下列事件處理常式:protected void SendEmailConfirmationToken(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); FailureText.Text = "Confirmation email sent. Please view the email and confirm your account."; ErrorMessage.Visible = true; ResendConfirm.Visible = false; } } }
LogIn
藉由變更以黃色醒目提示的程式碼,修改Login.aspx.cs程式碼後置中的事件處理常式,如下所示:protected void LogIn(object sender, EventArgs e) { if (IsValid) { // Validate the user password var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>(); // Require the user to have a confirmed email before they can log on. var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { FailureText.Text = "Invalid login attempt. You must have a confirmed email address. Enter your email and password, then press 'Resend Confirmation'."; ErrorMessage.Visible = true; ResendConfirm.Visible = true; } else { // This doen't count login failures towards account lockout // To enable password failures to trigger lockout, change to shouldLockout: true var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false); switch (result) { case SignInStatus.Success: IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); break; case SignInStatus.LockedOut: Response.Redirect("/Account/Lockout"); break; case SignInStatus.RequiresVerification: Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", Request.QueryString["ReturnUrl"], RememberMe.Checked), true); break; case SignInStatus.Failure: default: FailureText.Text = "Invalid login attempt"; ErrorMessage.Visible = true; break; } } } } }
新增以黃色醒目提示的程式碼,以更新 Login.aspx 頁面,如下所示:
<%@ Page Title="Log in" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebForms002.Account.Login" Async="true" %> <%@ Register Src="~/Account/OpenAuthProviders.ascx" TagPrefix="uc" TagName="OpenAuthProviders" %> <asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent"> <h2><%: Title %>.</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> <div class="form-horizontal"> <h4>Use a local account to log in.</h4> <hr /> <asp:PlaceHolder runat="server" ID="ErrorMessage" Visible="false"> <p class="text-danger"> <asp:Literal runat="server" ID="FailureText" /> </p> </asp:PlaceHolder> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Email" CssClass="col-md-2 control-label">Email</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Email" CssClass="form-control" TextMode="Email" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Email" CssClass="text-danger" ErrorMessage="The email field is required." /> </div> </div> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Password" CssClass="col-md-2 control-label">Password</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Password" TextMode="Password" CssClass="form-control" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Password" CssClass="text-danger" ErrorMessage="The password field is required." /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> <asp:CheckBox runat="server" ID="RememberMe" /> <asp:Label runat="server" AssociatedControlID="RememberMe">Remember me?</asp:Label> </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <asp:Button runat="server" OnClick="LogIn" Text="Log in" CssClass="btn btn-default" /> <asp:Button runat="server" ID="ResendConfirm" OnClick="SendEmailConfirmationToken" Text="Resend confirmation" Visible="false" CssClass="btn btn-default" /> </div> </div> </div> <p> <asp:HyperLink runat="server" ID="RegisterHyperLink" ViewStateMode="Disabled">Register as a new user</asp:HyperLink> </p> <p> <%-- Enable this once you have account confirmation enabled for password reset functionality --%> <asp:HyperLink runat="server" ID="ForgotPasswordHyperLink" ViewStateMode="Disabled">Forgot your password?</asp:HyperLink> </p> </section> </div> <div class="col-md-4"> <section id="socialLoginForm"> <uc:OpenAuthProviders runat="server" ID="OpenAuthLogin" /> </section> </div> </div> </asp:Content>
刪除 AspNetUsers 資料表中包含您想要測試的電子郵件別名的任何帳戶。
執行應用程式 (F5) 並註冊您的電子郵件地址。
透過剛傳送的電子郵件確認您的新帳戶之前,請先嘗試使用新帳戶登入。
您會看到您無法登入,而且您必須有確認的電子郵件帳戶。 此外,您現在可以將確認訊息重新傳送至電子郵件帳戶。輸入您的電子郵件地址和密碼,然後按 [重新傳送確認] 按鈕。
一旦您根據新傳送的電子郵件訊息確認電子郵件地址,請登入應用程式。
針對應用程式進行疑難排解
如果您沒有收到包含連結的電子郵件來驗證您的認證:
- 檢查您的垃圾郵件或垃圾郵件資料夾。
- 登入您的 SendGrid 帳戶,然後按一下[Email活動] 連結。
- 請務必使用 SendGrid 使用者帳戶名稱作為 Web.config 值,而不是您的 SendGrid 帳戶電子郵件地址。
其他資源
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應