前言:

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

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

微软文档: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] 特性构造函数参数为需进行对比的属性,此处用于确认修改后的密码。  

     public class ModifyModel
{
[UIHint("password")]
[Display(Name = "原密码")]
[Required]
public string OriginalPassword { get; set; } [Required]
[Display(Name = "新密码")]
[UIHint("password")]
public string ModifiedPassword { get; set; } [Required]
[Display(Name = "确认密码")]
[UIHint("password")]
[Compare("ModifiedPassword", ErrorMessage = "两次密码不匹配")]
public string ConfirmedPassword { get; set; }
}

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

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

         public IActionResult ModifyPassword()
{
ModifyModel model=new ModifyModel();
return View(model);
} [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ModifyPassword(ModifyModel model)
{
if (ModelState.IsValid)
{
string username = HttpContext.User.Identity.Name;
var student = _userManager.Users.FirstOrDefault(s => s.UserName == username);
var result =
await _userManager.ChangePasswordAsync(student, model.OriginalPassword, model.ModifiedPassword);
if (result.Succeeded)
{
await _signInManager.SignOutAsync();
return View("ModifySuccess");
}
ModelState.AddModelError("","原密码输入错误");
}
return View(model);
}

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

     @model ModifyModel

     @{
ViewData["Title"] = "ModifyPassword";
} @section Scripts{
<script>
$(document).ready(function() {
var $btn = $("#showPas");
var btn = $btn.get();
$btn.click(function() {
if (btn.checked) {
$(".pass").attr("type", "");
} else {
$(".pass").attr("type", "password");
}
});
})
</script>
} <h2>修改密码</h2> <div class="text-danger" asp-validation-summary="All"></div>
<form asp-action="ModifyPassword" method="post">
<div class="form-group">
<label asp-for="OriginalPassword"></label>
<input asp-for="OriginalPassword" class="pass"/>
</div>
<div class="form-group">
<label asp-for="ModifiedPassword"></label>
<input asp-for="ModifiedPassword" id="modifiedPassword" class="pass"/>
</div>
<div class="form-group">
<label asp-for="ConfirmedPassword"></label>
<input asp-for="ConfirmedPassword" id="confirmedPassword" onkeydown="" class="pass"/>
</div>
<div class="form-group">
<label>显示密码 </label><input style="margin-left: 10px" type="checkbox" id="showPas"/>
</div>
<input type="submit"/>
<input type="reset"/>
</form>

  随便建的 ModifySuccess 视图:

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

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

     @model Dictionary<string, object>
@{
ViewData["Title"] = "AccountInfo";
}
<h2>账户信息</h2>
<ul>
@foreach (var info in Model)
{
<li>@info.Key: @Model[info.Key]</li>
}
</ul>
<br />
<a class="btn btn-danger" asp-action="Logout">登出</a>
<a class="btn btn-primary" asp-action="ModifyPassword">修改密码</a>

Cookie 被清除:

 二、重置密码

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

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

 {
"Mail": {
"MailFromAddress": "",
"UseSsl": "false",
"Username": "",
"Password": "",
"ServerPort": "",
"ServerName": "smtp.163.com",
"UseDefaultCredentials": "true"
}
}

   各大邮箱运营商拥有自己的 SMTP 服务器,需要对应邮箱的请自行百度。这里仅展示 163 邮箱,这里请自行输入自己的 163 账号和密码。

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

     public class EmailSender
{
IConfiguration emailConfig = new ConfigurationBuilder().AddJsonFile("Mail.json").Build().GetSection("Mail");
public SmtpClient SmtpClient=new SmtpClient(); public EmailSender()
{
SmtpClient.EnableSsl = Boolean.Parse(emailConfig["UseSsl"]);
SmtpClient.UseDefaultCredentials = bool.Parse(emailConfig["UseDefaultCredentials"]);
SmtpClient.Credentials = new NetworkCredential(emailConfig["Username"], emailConfig["Password"]);
SmtpClient.Port = Int32.Parse(emailConfig["ServerPort"]);
SmtpClient.Host = emailConfig["ServerName"];
SmtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
}
}

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

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

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

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

     services.AddSingleton<EmailSender>();

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

     public enum RetrieveType
{
UserName,
Email
} public class RetrieveModel
{
[Required]
public RetrieveType RetrieveWay { get;set; }
[Required]
public string Account { get; set; }
}

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

     public class PasswordRetrieverController : Controller
{
private UserManager<Student> _userManager;
public EmailSender _emailSender; public PasswordRetrieverController(UserManager<Student> studentManager, EmailSender emailSender)
{
_userManager = studentManager;
_emailSender = emailSender;
} public IActionResult Retrieve()
{
RetrieveModel model = new RetrieveModel();
return View(model);
}

  Retrieve 视图:

     @model RetrieveModel

     <h2>找回密码</h2>
<hr/> <label class="text-danger">@ViewBag.Error</label> <form asp-action="RetrievePassword" asp-controller="PasswordRetriever" method="post">
<div class="form-group">
<input asp-for="Account" class="form-control" placeholder="请输入你的邮箱 / 账号 / 手机号"/>
</div>
<br/>
<div class="form-group">
<label>找回方式</label>
<select asp-for="RetrieveWay">
<option disabled value="">找回方式: </option>
<LoginType login-type="@Enum.GetNames(typeof(RetrieveType))"></LoginType>
</select>
</div>
<br/>
<input class="btn btn-primary" type="submit" value="确认"/>
<input class="btn btn-primary" type="reset"/>
</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: "xxxx@163.com", 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,由于此处为发送邮件,所以需要指定 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:

         public IActionResult ResetPassword(string userId,string code)
{
ResetPasswordModel model=new ResetPasswordModel()
{
UserId = userId,
Code = code
};
return View(model);
}

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

     @model ResetPasswordModel
@{
ViewData["Title"] = "ResetPassword";
} <h2>重置密码</h2> <form asp-action="ResetPassword" method="post" asp-antiforgery="true">
<div class="form-group">
@Html.HiddenFor(m=>m.Code)
@Html.HiddenFor(m=>m.UserId)
<label asp-for="Password"></label>
<input asp-for="Password"/>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword"/>
</div>
<input type="submit"/>
<input type="reset"/>
</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 方法和视图:

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

  最后向之前建立的 _LoginParitalView 视图中添加找回密码的按钮:

     @model LoginModel

     <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl"/>
<div class="form-group">
<label asp-for="Account"></label>
<input asp-for="Account" class="form-control" placeholder="请输入你的账号(学号) / 邮箱 / 手机号"/>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" placeholder="请输入你的密码"/>
</div>
<div class="form-group">
<label>登录方式</label>
<select asp-for="LoginType">
<option disabled value="">登录方式</option>
<LoginType login-type="@Enum.GetNames(typeof(LoginType))"></LoginType>
</select>
</div>
<input type="submit" class="btn btn-primary"/>
<input type="reset" class="btn btn-primary"/>
<a class="btn btn-success" asp-action="Retrieve" asp-controller="PasswordRetriever">找回密码</a>

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

  1. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  2. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(七) 学生信息增删

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  3. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(六)学生借阅/预约/查询书籍事务

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  4. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(五)外借/阅览图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  5. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(四)图书信息的增删改查

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  6. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(一) 基本模型以及数据库的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  7. 002.Create a web API with ASP.NET Core MVC and Visual Studio for Windows -- 【在windows上用vs与asp.net core mvc 创建一个 web api 程序】

    Create a web API with ASP.NET Core MVC and Visual Studio for Windows 在windows上用vs与asp.net core mvc 创 ...

  8. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

  9. Pro ASP.NET Core MVC 第6版 第二章(前半章)

    目录 第二章 第一个MVC 应用程序 学习一个软件开发框架的最好方法是跳进他的内部并使用它.在本章,你将用ASP.NET Core MVC创建一个简单的数据登录应用.我将它一步一步地展示,以便你能看清 ...

随机推荐

  1. HDU 5668 Circle

    中国剩余定理. 可以手动模拟一下每一次开始的人的编号和结束的人的编号. 每次删掉一个人,对剩下的人重新编号. 这样一次模拟下来,可以得到n个方程 形如:(u[i]+k)%(n-i+1)=v[i] 化简 ...

  2. strcpy c标准库函数

    C语言标准库函数strcpy,把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间. 已知strcpy函数的原型是: char *strcpy(char *dst, const ...

  3. Oracle: 通过命令行下载安装文件

    1. 导出oracle cookies 参考:https://blog.pythian.com/how-to-download-oracle-software-using-wget-or-curl/ ...

  4. 还原数据库出现“未获得排他訪问”解决方法(杀死数据库连接的存储过程sqlserver)

    在master数据库下创建存储步骤例如以下: createproc killspid (@dbnamevarchar(20)) as begin declare@sqlnvarchar(500) de ...

  5. sudo 用户添加

    sudo 用户添加 /etc/sudoers 在 ## Allow root to run any commands anywhere root    ALL=(ALL)   ALL 下面加上 xxx ...

  6. win10 localhost 解析为::1 的解决办法

    win10 localhost 解析为::1 的解决办法 学习了:https://blog.csdn.net/ambertian/article/details/70238020

  7. Ajax系列之四:问题总结

    1.最经典的就是ie下的缓存问题了.  假设使用的是get.那么在ie下出现缓存问题.导致代码仅仅运行一次. 解决的方法就是加时间戳或者随机数,使url变为唯一,这样就不会出现ie  下的缓存问题了, ...

  8. hi3531 SDK已编译文件系统制作jffs2文件系统镜像并解决这个问题 .

    一, 安装SDK 1.Hi3531 SDK包位置 在"Hi3531_V100R001***/01.software/board"文件夹下,您能够看到一个 Hi3531_SDK_Vx ...

  9. 【Mongodb教程 第八课 】MongoDB 更新文档

    MongoDB的 update() 和 save() 方法用于更新文档的集合. update()方法更新现有的文档值,而替换现有的文档通过的文件中 save() 方法. MongoDB Update( ...

  10. 【git体验】git原理及基础

    原理:分布式版本号控制系统像 Git,Mercurial,Bazaar 以及 Darcs 等,client并不仅仅提取最新版本号 的文件快照,而是把原始的代码仓库完整地镜像下来. 这么一来.不论什么一 ...