zoukankan      html  css  js  c++  java
  • ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(三)密码修改以及密码重置

     前言:

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

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

    微软文档: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>

  • 相关阅读:
    [skill][c][ld][gcc] 明确指定gcc在链接时明确使用静态库
    [administrative][lvm] lvm 分区修改
    [administrative] windows 下制作USB启动盘的工具
    [administrative][archlinux][netctl][wpa_supplicant] 查看WIFI链接信息
    [skill][makefile] makefile 常用内容记录
    [security] security engine things
    [administrative][CentOS] 新装系统时如何正确精准的选择基础环境和软件包
    [filesystem][archlinux][disk encryption][btrfs] btrfs
    [cipher][archlinux][disk encryption][btrfs] 磁盘分区加密 + btrfs
    [administrative][archlinux][clonezilla][disk cloning] 一块 windows 10 硬盘的备份
  • 原文地址:https://www.cnblogs.com/gokoururi/p/10380756.html
Copyright © 2011-2022 走看看