创建具有用户注册、电子邮件确认和密码重置功能的安全 ASP.NET Web 窗体应用 (C#)
作者 :Erik Reitan
本教程介绍如何使用 ASP.NET Identity 成员身份系统构建具有用户注册、电子邮件确认和密码重置的 ASP.NET Web Forms 应用。 本教程基于 Rick Anderson 的 MVC 教程。
简介
本教程指导你完成使用 Visual Studio 和 ASP.NET 4.5 创建 ASP.NET Web Forms应用程序所需的步骤,以创建具有用户注册、电子邮件确认和密码重置的安全Web Forms应用。
教程任务和信息:
创建 ASP.NET Web Forms应用
注意
警告:必须安装 Visual Studio 2013 Update 3 或更高版本才能完成本教程。
(文件 ->新建项目) 创建新项目,然后从“新建项目”对话框中选择 ASP.NET Web 应用程序模板和最新的.NET Framework版本。
从“新建 ASP.NET 项目”对话框中,选择Web Forms模板。 将默认身份验证保留为 “个人用户帐户”。 如果要在 Azure 中托管应用,请将“在云中托管检查”复选框保持选中。
然后,单击“ 确定 ”创建新项目。
为项目启用安全套接字层 (SSL) 。 按照使用 Web Forms 教程系列入门为 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确认仅提供对机器人的有限保护,不提供针对确定的垃圾邮件发送者的保护。
您通常希望阻止新用户在电子邮件、短信或其他机制确认之前将任何数据发布到您的网站。 在以下部分中,我们将启用电子邮件确认并修改代码,以防止新注册的用户在确认其电子邮件之前登录。 本教程将使用电子邮件服务 SendGrid。
挂钩 SendGrid
自编写本教程以来,SendGrid 已更改其 API。 有关当前的 SendGrid 说明,请参阅 SendGrid 或 启用帐户确认和密码恢复。
虽然本教程仅介绍如何通过 SendGrid 添加电子邮件通知,但你可以使用 SMTP 和其他机制发送电子邮件, (查看 其他资源) 。
在 Visual Studio 中,打开 包管理器控制台 (工具 ->NuGet 包管理器 ->包管理器控制台) ,然后输入以下命令:
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 文件夹中的 Forgot.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 帐户电子邮件地址。