ASP.NET Core 打造一個簡單的圖書館管理系統(四)密碼修改以及密碼重置


 前言:

本系列文章主要為我之前所學知識的一次微小的實踐,以我學校圖書館管理系統為雛形所作。

本系列文章主要參考資料:

微軟文檔: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 的使用、配置文件的使用

 

一、添加密碼修改功能

  首先創建對應的視圖模型:

  其中 [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: "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。

 

  為使用該 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>

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM