Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

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

NanaseRuri 2019-02-14 21:09:00 阅读数:152 评论数:0 点赞数:0 收藏数:0

 前言:

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

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

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

《Pro ASP.NET MVC 5》、《锋利的 jQuery》

 

 

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

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

 

 

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

 

一、添加密码修改功能

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

其中 16 行的 [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 }

 

利用 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 }

各大邮箱运营商拥有自己的 SMTP 服务器,需要对应邮箱的请自行百度。这里仅展示 163 邮箱,这里请自行输入自己的 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 并发送邮件:

 

 1  [HttpPost]
 2  [ValidateAntiForgeryToken]
 3 public async Task<IActionResult> RetrievePassword(RetrieveModel model)
 4  {
 5 bool sendResult=false;
 6 if (ModelState.IsValid)
 7  {
 8 Student student = new Student();
 9 switch (model.RetrieveWay)
10  {
11 case RetrieveType.UserName:
12 student = await _userManager.FindByNameAsync(model.Account);
13 if (student != null)
14  {
15 string code = await _userManager.GeneratePasswordResetTokenAsync(student);
16 sendResult = await SendEmail(student.Id, code, student.Email);
17  }
18 break;
19 case RetrieveType.Email:
20 student = await _userManager.FindByEmailAsync(model.Account);
21 if (student != null)
22  {
23 string code = await _userManager.GeneratePasswordResetTokenAsync(student);
24 sendResult = await SendEmail(student.Id, code, student.Email);
25  }
26 break;
27  }
28 if (student == null)
29  {
30 ViewBag.Error("用户不存在,请重新输入");
31 return View("Retrieve",model);
32  }
33  }
34 ViewBag.Message = "已发送邮件至您的邮箱,请注意查收";
35 ViewBag.Failed = "信息发送失败";
36 return View(sendResult);
37 } 

 

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

 1 async Task<bool> SendEmail(string userId, string code, string mailAddress)
 2  {
 3 Student student = await _userManager.FindByIdAsync(userId);
 4 if (student!=null)
 5  {
 6 string url = Url.Action("ResetPassword","PasswordRetriever",new{userId=userId,code=code}, Url.ActionContext.HttpContext.Request.Scheme);
 7 StringBuilder sb = new StringBuilder();
 8 sb.AppendLine($" 请点击<a href=\"{url}\">此处</a>重置您的密码");
 9 MailMessage message = new MailMessage(from: "xxxx@163.com", to: mailAddress, subject: "重置密码", body: sb.ToString());
10 message.BodyEncoding=Encoding.UTF8;
11 message.IsBodyHtml = true;
12 try
13  {
14  _emailSender.SmtpClient.Send(message);
15  }
16 catch (Exception e)
17  {
18 return false;
19  }
20
21 return true;
22  }
23 return false;
24 }

为 Url.Action 方法指定 protocol 参数以生成完整 url ,否则只会生成相对 url,由于此处为发送邮件,所以需要指定 url 为绝对 Url。

 

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

 public class ResetPasswordModel
{
public string Code { get; set; }
public string UserId { get; set; }
[Required]
[Display(Name="密码")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[Display(Name = "确认密码")]
[DataType(DataType.Password)]
[Compare("Password",ErrorMessage = "两次密码不匹配")]
public string ConfirmPassword { get; set; }
}

 

定义用来重置密码的方法 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 对象:

 [ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
{
if (ModelState.IsValid)
{
var user = _userManager.FindByIdAsync(model.UserId);
if (user!=null)
{
var result = await _userManager.ResetPasswordAsync(user.Result, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(ResetSuccess));
}
}
}
return View(model);
}

 

随便定义的用以表示更改成功的 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>

 

 

 

版权声明
本文为[NanaseRuri]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/gokoururi/p/10380756.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领