ASP.NET Core 打造一个简单的图书馆管理系统(四)密码修改以及密码重置

 前言:

  本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。

  本系列文章主要参考资料:

  微软文档:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows

  《Pro ASP.NET MVC 5》、《Bootstrap 开发精解》、《锋利的 jQuery》

  此系列皆使用 VS2017+C# 作为开发环境。如果有什么问题或者意见欢迎在留言区进行留言。 

  项目 github 地址:https://github.com/NanaseRuri/LibraryDemo

  本章内容:Identity 修改密码和找回密码、c# SMTP 的使用、配置文件的使用

一、添加密码修改功能

  首先创建对应的视图模型:

  其中 [Compare] 特性构造函数参数为需进行对比的属性,此处用于确认修改后的密码。  

 1     public class ModifyModel
 2     {
 3         [UIHint("password")]
 4         [Display(Name = "原密码")]
 5         [Required]
 6         public string OriginalPassword { get; set; } 7 8  [Required] 9 [Display(Name = "新密码")] 10 [UIHint("password")] 11 public string ModifiedPassword { get; set; } 12 13  [Required] 14 [Display(Name = "确认密码")] 15 [UIHint("password")] 16 [Compare("ModifiedPassword", ErrorMessage = "两次密码不匹配")] 17 public string ConfirmedPassword { get; set; } 18 }

  在 StudentAccountController 中添加 [Authorize] 特性,然后可以去除 StudentAccountController 中方法的 [Authorize] 特性。当方法不需要授权即可访问时添加 [AllowAnonymous] 特性。

1     [Authorize]
2     public class StudentAccountController : Controller

  利用 Identity 框架中 UserManager 对象的 ChangePasswordAsync 方法用来修改密码,该方法返回一个 IdentityResult 对象,可通过其 Succeeded 属性查看操作是否成功。在此修改成功后调用 _signInManager.SignOutAsync() 方法来清除当前 Cookie。

  定义用于修改密码的动作方法和视图:

 1         public IActionResult ModifyPassword()
 2         {
 3             ModifyModel model=new ModifyModel();
 4             return View(model);
 5         }
 6 
 7  [HttpPost] 8  [ValidateAntiForgeryToken] 9 public async Task<IActionResult> ModifyPassword(ModifyModel model) 10  { 11 if (ModelState.IsValid) 12  { 13 string username = HttpContext.User.Identity.Name; 14 var student = _userManager.Users.FirstOrDefault(s => s.UserName == username); 15 var result = 16 await _userManager.ChangePasswordAsync(student, model.OriginalPassword, model.ModifiedPassword); 17 if (result.Succeeded) 18  { 19 await _signInManager.SignOutAsync(); 20 return View("ModifySuccess"); 21  } 22 ModelState.AddModelError("","原密码输入错误"); 23  } 24 return View(model); 25 }

   ModifyPassword 视图,添加用以表示是否显示密码的复选框,并使用 jQuery 和 JS 添加相应的事件。将<script></script>标签统一放在 @section Scripts 以方便地使用布局:

 1     @model ModifyModel
 2 
 3     @{
 4         ViewData["Title"] = "ModifyPassword";
 5     }
 6 
 7     @section Scripts{ 
 8         <script>
 9  $(document).ready(function() { 10 var $btn = $("#showPas"); 11 var btn = $btn.get(0); 12  $btn.click(function() { 13 if (btn.checked) { 14 $(".pass").attr("type", ""); 15 } else { 16 $(".pass").attr("type", "password"); 17  } 18  }); 19  }) 20 </script> 21  } 22 23 24 <h2>修改密码</h2> 25 26 <div class="text-danger" asp-validation-summary="All"></div> 27 <form asp-action="ModifyPassword" method="post"> 28 <div class="form-group"> 29 <label asp-for="OriginalPassword"></label> 30 <input asp-for="OriginalPassword" class="pass"/> 31 </div> 32 <div class="form-group"> 33 <label asp-for="ModifiedPassword"></label> 34 <input asp-for="ModifiedPassword" id="modifiedPassword" class="pass"/> 35 </div> 36 <div class="form-group"> 37 <label asp-for="ConfirmedPassword"></label> 38 <input asp-for="ConfirmedPassword" id="confirmedPassword" onkeydown="" class="pass"/> 39 </div> 40 <div class="form-group"> 41 <label>显示密码 </label><input style="margin-left: 10px" type="checkbox" id="showPas"/> 42 </div> 43 <input type="submit"/> 44 <input type="reset"/> 45 </form>

   随便建的 ModifySuccess 视图:

1     @{
2         ViewData["Title"] = "修改成功";
3     }
4 
5     <h2>修改成功</h2>
6 
7     <h4><a asp-action="Login">请重新登录</a></h4>

   然后修改 AccountInfo 视图以添加对应的修改密码的按钮:

 1     @model Dictionary<string, object>
 2     @{
 3         ViewData["Title"] = "AccountInfo";
 4     }
 5     <h2>账户信息</h2>
 6     <ul>
 7         @foreach (var info in Model)
 8         {
 9             <li>@info.Key: @Model[info.Key]</li>
10  } 11 </ul> 12 <br /> 13 <a class="btn btn-danger" asp-action="Logout">登出</a> 14 <a class="btn btn-primary" asp-action="ModifyPassword">修改密码</a>

Cookie 被清除:

 二、重置密码

  在 Identity 框架中, UserManager 提供了 GeneratePasswordResetTokenAsync 以及 ResetPasswordAsync 方法用以重置密码。

  现实生活中,一般通过邮件发送重置连接来重置密码,为日后更方便地配置,在此创建 Mail.json

 1   {
 2     "Mail": {
 3       "MailFromAddress": "",
 4       "UseSsl": "false",
 5       "Username": "",
 6       "Password": "", 7 "ServerPort": "25", 8 "ServerName": "smtp.163.com", 9 "UseDefaultCredentials": "true" 10  } 11 }

   这里请自行输入自己的 163 账号和密码。

  然后创建一个类用来配置发送邮件的相关信息:

 1     public class EmailSender
 2     {
 3         IConfiguration emailConfig = new ConfigurationBuilder().AddJsonFile("Mail.json").Build().GetSection("Mail");
 4         public SmtpClient SmtpClient=new SmtpClient();
 5 
 6         public EmailSender()
 7  { 8 SmtpClient.EnableSsl = Boolean.Parse(emailConfig["UseSsl"]); 9 SmtpClient.UseDefaultCredentials = bool.Parse(emailConfig["UseDefaultCredentials"]); 10 SmtpClient.Credentials = new NetworkCredential(emailConfig["Username"], emailConfig["Password"]); 11 SmtpClient.Port = Int32.Parse(emailConfig["ServerPort"]); 12 SmtpClient.Host = emailConfig["ServerName"]; 13 SmtpClient.DeliveryMethod = SmtpDeliveryMethod.Network; 14  } 15 }

  该类定义了一个读取配置的字段,以及一个用来发送邮件的 SmtpClient 属性。

  此处第三行将会从 bin 文件夹中读取 Mail.json 文件中的 Mail 节点,为使 ConfigurationBuilder 能够读取到 bin 文件夹的文件,需要将 Mail.json 设置为复制到输出目录中:

  然后该类将在构造函数对 SmtpClient 进行相应的配置。注意需要在为 SmtpClient 的 Credentials 属性赋值前为 UseDefaultCredentials 赋值,否则 Credentials 将被赋值为空值而出 Bug。

  为使整个网页应用在整个生命期内使用的是同一个 SmtpClient 实例,在 ConfigureServices 中进行配置:  

1             services.AddSingleton<EmailSender>();

  创建用于确定找回途径的模型:

 1     public enum RetrieveType
 2     {
 3         UserName,
 4         Email
 5     }
 6 
 7     public class RetrieveModel 8  { 9  [Required] 10 public RetrieveType RetrieveWay { get;set; } 11  [Required] 12 public string Account { get; set; } 13 }

  定义一个 PasswordRetrieverController 专门用以处理找回密码的逻辑,Retrieve 方法创建接收用户信息输入的视图:

 1     public class PasswordRetrieverController : Controller
 2     {
 3         private UserManager<Student> _userManager;
 4         public EmailSender _emailSender;
 5 
 6         public PasswordRetrieverController(UserManager<Student> studentManager, EmailSender emailSender)
 7  { 8 _userManager = studentManager; 9 _emailSender = emailSender; 10  } 11 12 public IActionResult Retrieve() 13  { 14 RetrieveModel model = new RetrieveModel(); 15 return View(model); 16 }

  Retrieve 视图:

 1     @model RetrieveModel
 2 
 3     <h2>找回密码</h2>
 4     <hr/>
 5 
 6     <label class="text-danger">@ViewBag.Error</label>
 7 
 8     <form asp-action="RetrievePassword" asp-controller="PasswordRetriever" method="post">
 9         <div class="form-group">
10             <input asp-for="Account" class="form-control" placeholder="请输入你的邮箱 / 账号 / 手机号"/>        
11         </div>
12         <br/>
13         <div class="form-group">
14             <label>找回方式</label>
15             <select asp-for="RetrieveWay">
16                 <option disabled value="">找回方式: </option>
17                 <LoginType login-type="@Enum.GetNames(typeof(RetrieveType))"></LoginType>
18             </select>
19         </div>
20         <br/>
21         <input class="btn btn-primary" type="submit" value="确认"/>
22         <input class="btn btn-primary" type="reset"/>
23     </form>

  定义用来进行具体逻辑验证的 RetrievePassword 方法,该方法验证用户是否存在,生成用以重置密码的 token 并发送邮件:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> RetrievePassword(RetrieveModel model)
        {
            bool sendResult=false;
            if (ModelState.IsValid)
            {
                Student student = new Student();
                switch (model.RetrieveWay) { case RetrieveType.UserName: student = await _userManager.FindByNameAsync(model.Account); if (student != null) { string code = await _userManager.GeneratePasswordResetTokenAsync(student); sendResult = await SendEmail(student.Id, code, student.Email); } break; case RetrieveType.Email: student = await _userManager.FindByEmailAsync(model.Account); if (student != null) { string code = await _userManager.GeneratePasswordResetTokenAsync(student); sendResult = await SendEmail(student.Id, code, student.Email); } break; } if (student == null) { ViewBag.Error("用户不存在,请重新输入"); return View("Retrieve",model); } } ViewBag.Message = "已发送邮件至您的邮箱,请注意查收"; ViewBag.Failed = "信息发送失败"; return View(sendResult); }

  在 PasswordRetrieverController 中定义用以发送邮件的方法,以 bool 为返回值以判断邮件是否发送成功,此处 MailMessage 处的 from 参数请自行配置:

        async Task<bool> SendEmail(string userId, string code, string mailAddress)
        {
            Student student = await _userManager.FindByIdAsync(userId);
            if (student!=null)
            {
                string url = Url.Action("ResetPassword","PasswordRetriever",new{userId=userId,code=code}, Url.ActionContext.HttpContext.Request.Scheme);
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"  请点击<a href=\"{url}\">此处</a>重置您的密码");
                MailMessage message = new MailMessage(from: "[email protected]", to: mailAddress, subject: "重置密码", body: sb.ToString());
                message.BodyEncoding=Encoding.UTF8;
                message.IsBodyHtml = true;
                try
                {
                    _emailSender.SmtpClient.Send(message);
                }
                catch (Exception e)
                {
                    return false;
                }

                return true;
            }
            return false;
        }

  为 Url.Action 方法指定 protocol 参数以生成完整 url ,否则只会生成相对 url。

  为使用该 token,创建专门用于重置密码的模型,其中 Code 用来接收 GeneratePasswordResetTokenAsync 生成的 token,UserId 用来传递待重置用户的 Id:

 1     public class ResetPasswordModel
 2     {
 3         public string Code { get; set; }
 4 
 5         public string UserId { get; set; }
 6 
 7         [Required]
 8         [Display(Name="密码")]
 9         [DataType(DataType.Password)]
10         public string Password { get; set; }
11 
12         [Required]
13         [Display(Name = "确认密码")]
14         [DataType(DataType.Password)]
15         [Compare("Password",ErrorMessage = "两次密码不匹配")]
16         public string ConfirmPassword { get; set; }
17     }

  定义用来重置密码的方法 ResetPassword:

1         public IActionResult ResetPassword(string userId,string code)
2         {
3             ResetPasswordModel model=new ResetPasswordModel()
4             {
5                 UserId = userId,
6                 Code = code 7  }; 8 return View(model); 9 }

  ResetPassword 视图,此视图将 token 和userId 设置为隐藏字段以在请求中传递:

 1     @model ResetPasswordModel
 2     @{
 3         ViewData["Title"] = "ResetPassword";
 4     }
 5 
 6     <h2>重置密码</h2>
 7 
 8     <form asp-action="ResetPassword" method="post" asp-antiforgery="true">
 9         <div class="form-group">
10             @Html.HiddenFor(m=>m.Code)
11             @Html.HiddenFor(m=>m.UserId) 12 <label asp-for="Password"></label> 13 <input asp-for="Password"/> 14 </div> 15 <div class="form-group"> 16 <label asp-for="ConfirmPassword"></label> 17 <input asp-for="ConfirmPassword"/> 18 </div> 19 <input type="submit"/> 20 <input type="reset"/> 21 </form>

  定义用以具体逻辑验证的 ResetPassword 方法,UserManager<T> 对象的 ResetPasswordAsync 方法接收一个 T类型对象、一个 token 字符串以及密码,返回 IdentityResult 对象:

 1         [ValidateAntiForgeryToken]
 2         [HttpPost]
 3         public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
 4         {
 5             if (ModelState.IsValid)
 6  { 7 var user = _userManager.FindByIdAsync(model.UserId); 8 if (user!=null) 9  { 10 var result = await _userManager.ResetPasswordAsync(user.Result, model.Code, model.Password); 11 if (result.Succeeded) 12  { 13 return RedirectToAction(nameof(ResetSuccess)); 14  } 15  } 16  } 17 return View(model); 18 }

  随便定义的 ResetSuccess 方法和视图:

 1         public IActionResult ResetSuccess()
 2         {
 3             return View();
 4         }
1     @{
2         ViewData["Title"] = "ResetSuccess";
3     }
4 
5     <h2>重置成功</h2>
6 
7     <h3>点击<a asp-action="Login" asp-controller="StudentAccount" target="_blank">此处</a>进行登录</h3>

  最后向 _LoginParitalView 添加找回密码的按钮:

 1 @model LoginModel
 2 
 3     <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl"/>
 4     <div class="form-group">   
 5         <label asp-for="Account"></label>
 6         <input asp-for="Account" class="form-control" placeholder="请输入你的账号(学号) / 邮箱 / 手机号"/>
 7     </div>
 8     <div class="form-group">   
 9         <label asp-for="Password"></label>
10         <input asp-for="Password" class="form-control" placeholder="请输入你的密码"/>
11     </div>
12     <div class="form-group">
13         <label>登录方式</label>
14         <select asp-for="LoginType">
15             <option disabled value="">登录方式</option>
16             <LoginType login-type="@Enum.GetNames(typeof(LoginType))"></LoginType>
17         </select>
18     </div>
19     <input type="submit" class="btn btn-primary"/>
20     <input type="reset" class="btn btn-primary"/>
21     <a class="btn btn-success" asp-action="Retrieve" asp-controller="PasswordRetriever">找回密码</a>

猜你喜欢

转载自www.cnblogs.com/gokoururi/p/10153040.html
今日推荐