1. 程式人生 > >ASP.NET Core 打造一個簡單的圖書館管理系統(三)基本登入頁面以及授權邏輯的建立

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 框架的配置、對賬戶進行授權的配置、資料庫的初始化方法、自定義 TagHelper、Identity 找回密碼、c# SMTP 的使用、配置檔案的使用

 

 

 一到四為對 Student 即 Identity框架的使用,第五節為對 Admin 使用者的配置

 

 

 

一、自定義賬號和密碼的限制

  在 Startup.cs 的 ConfigureServices 方法中可以對 Identity 的賬號和密碼進行限制:

 1             services.AddIdentity<Student, IdentityRole>(opts =>
 2
{ 3 opts.User.RequireUniqueEmail = true; 4 opts.User.AllowedUserNameCharacters = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789"; 5 opts.Password.RequiredLength = 6; 6 opts.Password.RequireNonAlphanumeric = false
; 7 opts.Password.RequireLowercase = false; 8 opts.Password.RequireUppercase = false; 9 opts.Password.RequireDigit = false; 10 }).AddEntityFrameworkStores<StudentIdentityDbContext>() 11 .AddDefaultTokenProviders();

  RequireUniqueEmail 限制每個郵箱只能用於一個賬號。

  此處 AllowedUserNameCharacters 方法限制使用者名稱能夠使用的字元,需要單獨輸入每個字元。

  剩下的設定分別為限制密碼必須有符號 / 包含小寫字母 / 包含大寫字母 / 包含數字。

 

 

 

 

二、對資料庫進行初始化

  在此建立一個 DatabaseInitiator 用以對資料庫進行初始化:

 1         public static async Task Initial(IServiceProvider serviceProvider)
 2         {
 3             UserManager<Student> userManager = serviceProvider.GetRequiredService<UserManager<Student>>();
 4             if (userManager.Users.Any())
 5             {
 6                 return;
 7             }
 8             IEnumerable<Student> initialStudents = new[]
 9             {
10                 new Student()
11                 {
12                     UserName = "U201600001",
13                     Name = "Nanase",
14                     Email = "[email protected]",
15                     PhoneNumber = "12345678910",
16                     Degree = Degrees.CollegeStudent,
17                     MaxBooksNumber = 10,
18                 },
19                 new Student()
20                 {
21                     UserName = "U201600002",
22                     Name = "Ruri",
23                     Email = "[email protected]",
24                     PhoneNumber = "12345678911",
25                     Degree = Degrees.DoctorateDegree,
26                     MaxBooksNumber = 15
27                 },
28             };
29 
30             foreach (var student in initialStudents)
31             {
32                 await userManager.CreateAsync(student, student.UserName.Substring(student.UserName.Length - 6,6));
33             }
34         }

 

  為確保能夠進行初始化,在 Configure 方法中呼叫該靜態方法:  

1             app.UseMvc(routes =>
2             {
3                 routes.MapRoute(
4                     name: "default",
5                     template: "{controller=Home}/{action=Index}/{id?}");
6             });
7             DatabaseInitiator.Initial(app.ApplicationServices).Wait();

  Initial 方法中 serviceProvider 引數將在傳入 ConfigureServices 方法呼叫後的 ServiceProvider,此時在 Initial 方法中初始化的資料也會使用 ConfigureServices 中對賬號和密碼的限制。

  此處我們使用賬號的後六位作為密碼。啟動網頁後檢視資料庫的資料:

 

 

 

 

三、建立驗證所用的控制器以及檢視

  首先建立一個檢視模型用於儲存賬號的資訊,為了方便實現多種登入方式,此處建立一個 LoginType 列舉:

  [UIHint] 特性建構函式傳入一個字串用來告知在 <input/> 中時用什麼模板來展示資料。

    public enum LoginType
    {
        UserName,
        Email,
        Phone
    }

    public class LoginModel
    {
        [Required(ErrorMessage = "請輸入您的學號 / 郵箱 / 手機號碼")]
        [Display(Name = "學號 / 郵箱 / 手機號碼")]
        public string Account { get; set; }

        [Required(ErrorMessage = "請輸入您的密碼")]
        [UIHint("password")]
        [Display(Name = "密碼")]
        public string Password { get; set; }

        [Required]
        public LoginType LoginType { get; set; }
    }

 

   使用支架特性建立一個 StudentAccountController

 

 1     public class StudentAccountController : Controller
 2     {
 3         public IActionResult Login(string returnUrl)
 4         {
 5             LoginModel loginInfo=new LoginModel();
 6             ViewBag.returnUrl = returnUrl;
 7             return View(loginInfo);
 8         }
 9   }

 

  先建立普通的 Login 檢視:

 1 @model LoginModel
 2 
 3 @{
 4     ViewData["Title"] = "Login";
 5 }
 6 
 7 <h2>Login</h2>
 8 <br/>
 9 <div class="text-danger" asp-validation-summary="All"></div>
10 <br/>
11 <form asp-action="Login" method="post">
12     <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl"/>
13     <div class="form-group">   
14         <label asp-for="Account"></label>
15         <input asp-for="Account" class="form-control" placeholder="請輸入你的學號 / 郵箱 / 手機號"/>
16     </div>
17     <div class="form-group">   
18         <label asp-for="Password"></label>
19         <input asp-for="Password" class="form-control" placeholder="請輸入你的密碼"/>
20     </div>
21     <div class="form-group">
22         <label>登入方式</label>
23         <select asp-for="LoginType">
24             <option disabled value="">登入方式</option>
25             <LoginType login-type="@Enum.GetNames(typeof(LoginType))"></LoginType>
26         </select>
27     </div>
28     <input type="submit" class="btn btn-primary"/>
29 </form>

  在此為新增多種登入方式,並使檢視更加清晰,建立了一個 LoginTypeTagHelper ,TagHelper 可制定自定義 HTML 標記並在最終生成檢視時轉換成標準的 HTML 標記。

 1     [HtmlTargetElement("LoginType")]
 2     public class LoginTypeTagHelper:TagHelper
 3     {
 4         public string[] LoginType { get; set; }
 5 
 6         public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
 7         {
 8             foreach (var loginType in LoginType)
 9             {
10                 switch (loginType)
11                 {
12                     case "UserName": output.Content.AppendHtml($"<option selected=\"selected/\" value=\"{loginType}\">學號</option>");
13                         break;
14                     case "Email": output.Content.AppendHtml(GetOption(loginType, "郵箱"));
15                         break;
16                     case "Phone": output.Content.AppendHtml(GetOption(loginType, "手機號碼"));
17                         break;
18                     default: break;
19                 }                
20             }            
21             return Task.CompletedTask;
22         }
23 
24         private static string GetOption(string loginType,string innerText)
25         {
26             return $"<option value=\"{loginType}\">{innerText}</option>";
27         }
28     }

 

 

  然後建立一個用於對資訊進行驗證的動作方法。

  為了獲取資料庫的資料以及對資料進行驗證授權,需要通過 DI(依賴注入) 獲取對應的 UserManager 和 SignInManager 物件,在此針對 StudentAccountController 的建構函式進行更新。

  StudentAccountController 整體:

 1 public class StudentAccountController : Controller
 2     {
 3         private UserManager<Student> _userManager;
 4         private SignInManager<Student> _signInManager;
 5 
 6         public StudentAccountController(UserManager<Student> studentManager, SignInManager<Student> signInManager)
 7         {
 8             _userManager = studentManager;
 9             _signInManager = signInManager;
10         }
11         
12         public IActionResult Login(string returnUrl)
13         {
14             LoginModel loginInfo = new LoginModel();
15             ViewBag.returnUrl = returnUrl;
16             return View(loginInfo);
17         }
18 
19         [HttpPost]
20         [ValidateAntiForgeryToken]
21         public async Task<IActionResult> Login(LoginModel loginInfo, string returnUrl)
22         {
23             if (ModelState.IsValid)
24             {
25                 Student student =await GetStudentByLoginModel(loginInfo);
26 
27                 if (student == null)
28                 {
29                     return View(loginInfo);
30                 }
31                 SignInResult signInResult = await _signInManager.PasswordSignInAsync(student, loginInfo.Password, false, false);
32 
33                 if (signInResult.Succeeded)
34                 {
35                     return Redirect(returnUrl ?? "/StudentAccount/"+nameof(AccountInfo));
36                 }
37 
38                 ModelState.AddModelError("", "賬號或密碼錯誤");
39                             
40             }
41 
42             return View(loginInfo);
43         }
44 
45         [Authorize]
46         public IActionResult AccountInfo()
47         {
48             return View(CurrentAccountData());
49         }
50 
51         Dictionary<string, object> CurrentAccountData()
52         {
53             var userName = HttpContext.User.Identity.Name;
54             var user = _userManager.FindByNameAsync(userName).Result;
55 
56             return new Dictionary<string, object>()
57             {
58                 ["學號"]=userName,
59                 ["姓名"]=user.Name,
60                 ["郵箱"]=user.Email,
61                 ["手機號"]=user.PhoneNumber,
62             };
63         }

  _userManager 以及  _signInManager 將通過 DI 獲得例項;[ValidateAntiForgeryToken] 特性用於防止 XSRF 攻擊;returnUrl 引數用於接收或返回之前正在訪問的頁面,在此處若 returnUrl 為空則返回 AccountInfo 頁面;[Authorize] 特性用於確保只有已授權的使用者才能訪問對應動作方法;CurrentAccountData 方法用於獲取當前使用者的資訊以在 AccountInfo 檢視中呈現。

 

  由於未進行授權,在此直接訪問 AccountInfo 方法預設會返回 /Account/Login 頁面請求驗證,可通過在 ConfigureServices 方法進行配置以覆蓋這一行為,讓頁面預設返回 /StudentAccount/Login :

1             services.ConfigureApplicationCookie(opts =>
2             {
3                 opts.LoginPath = "/StudentAccount/Login";
4             });

 

  為了使 [Authorize] 特效能夠正常工作,需要在 Configure 方法中使用 Authentication 中介軟體,如果沒有呼叫app.UseAuthentication(),則訪問帶有 [Authorize] 的方法會再度要求進行驗證。中介軟體的順序很重要:

1             app.UseAuthentication();
2             app.UseHttpsRedirection();
3             app.UseStaticFiles();
4             app.UseCookiePolicy();

 

  同時在 ConfigureServices 中對 Cookie 策略進行配置:

1             services.Configure<CookiePolicyOptions>(options =>
2             {
3                 options.CheckConsentNeeded = context => true;
4                 options.MinimumSameSitePolicy = SameSiteMode.None;
5             });

 

  直接訪問 AccountInfo 頁面:

 

  輸入賬號密碼進行驗證:

 

  驗證之後返回 /StudentAccount/AccountInfo 頁面:

 

 

 

四、建立登出網頁

  簡單地呼叫 SignOutAsync 用以清除當前 Cookie 中的授權資訊。

1         [Authorize]
2         public async Task<IActionResult> Logout()
3         {
4             await _signInManager.SignOutAsync();
5             return View("Login");
6         }

 

  同時在 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>

 

 

  登出後返回 Login 頁面,同時 AccountInfo 頁面需要重新進行驗證。

 

  附加使用郵箱以及手機號驗證的測試:

 

 

  最後對 Login 動作方法進行修改以避免不必要的驗證:

 1         public IActionResult Login(string returnUrl)
 2         {
 3             if (HttpContext.User.Identity.IsAuthenticated)
 4             {
 5                 return RedirectToAction("AccountInfo");
 6             }
 7 
 8             LoginModel loginInfo = new LoginModel();
 9             ViewBag.returnUrl = returnUrl;
10             return View(loginInfo);
11         }

 

  已授權情況下再度訪問 Login 方法返回 AccountInfo :

 

  登出後再次訪問 AccountInfo 方法: 

 

 

  登出後需要重新驗證:

 

 

 

五?、Admin,不可與 Identity 同時使用的基於 Cookie 的授權?

  帶有自定義驗證邏輯專案地址:https://files-cdn.cnblogs.com/files/gokoururi/LibraryDemo-Failed.zip

 

  本來打算使用 Cookie 進行對 Admin 的授權,但由於 Identity 使用的也是基於 Cookie 的授權並做了大量的工作,同時使用兩者在一些奇奇怪怪的地方會出現 bug,如果有什麼解決方案感謝不盡,因此這節只做使用 Cookie 授權的演示。

 

  為使用 Cookie 授權,需要在 ConfigureServices 和 Configure 方法中進行配置:

  ConfigureServices 中呼叫 services.AddAuthentication 啟用驗證,使用 CookieAuthenticationDefaults.AuthenticationScheme 作為預設該驗證的 scheme,使用預設 Cookie 沿驗證。

1             services.AddAuthentication(options =>
2                 {
3                     options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
4                 })
5                 .AddCookie();

 

  為保證安全,密碼不能使用明文儲存在資料庫中,因此在此使用 MD5 加密對密碼進行加密。在此建立一個類用以更方便地呼叫:

  建立 Encrptor 類,設定私有預設建構函式防止該類被例項化,新增靜態方法 MD5Encrypt32 用以返回加密後的字串:

 1     public class Encryptor
 2     {
 3         private Encryptor()
 4         {
 5         }
 6 
 7         public static string MD5Encrypt(string password)
 8         {
 9             MD5 md5 = MD5.Create();
10             byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
11             StringBuilder hashPassword = new StringBuilder();
12             foreach (var b in hashBytes)
13             {
14                 hashPassword.Append(b);
15             }
16 
17             return hashPassword.ToString();
18         }
19     }

 

  在此處要注意使用 context.SaveChanges 來儲存對資料庫做出的增刪改的操作,否則資料庫將不會做出更改。對 AdminDbContext 進行初始化:

 1     public class AdminInitiator
 2     {
 3         public static async Task InitialAdmins(IServiceProvider serviceProvider)
 4         {
 5             AdminDbContext adminDbContext = serviceProvider.GetRequiredService<AdminDbContext>();
 6             if (adminDbContext.Admins.Any())
 7             {
 8                 return;
 9             }
10 
11             IEnumerable<Admin> admins = new[]
12             {
13                 new Admin()
14                 {
15                     UserName = "admin",
16                     Email = "[email protected]",
17                     PhoneNumber = "10000000000",
18                     Password = "123456"
19                 },
20                 new Admin()
21                 {
22                     UserName = "admin1",
23                     Email = "[email protected]",
24                     PhoneNumber = "10000000001",
25                     Password = "456789"
26                 },
27             };
28 
29             foreach (var admin in admins)
30             {
31                 EncryptAdmin(admin);
32                 await adminDbContext.AddAsync(admin);
33                 await adminDbContext.SaveChangesAsync();
34             }            
35         }
36 
37         private static Admin EncryptAdmin(Admin admin)
38         {
39             admin.Password = Encryptor.MD5Encrypt(admin.Password);
40             return admin;
41         }
42     }

 

  此處為 Authorize 特性指定授權的 Scheme,則可以通過不同的 Scheme 指定不同的授權。指定 [AllowAnoymous] 特性時,該方法可以在未授權的情況下被訪問。

  1     [Authorize(AuthenticationSchemes=CookieAuthenticationDefaults.AuthenticationScheme)]
  2     public class AdminAccountController : Controller
  3     {
  4         private AdminDbContext _context;
  5 
  6         public AdminAccountController(AdminDbContext context)
  7         {
  8             _context = context;
  9         }
 10 
 11         [AllowAnonymous]
 12         public IActionResult Login(string returnUrl)
 13         {
 14             if (HttpContext.User.IsInRole("admin"))
 15             {
 16                 return RedirectToAction("Index");
 17             }
 18             LoginModel model = new LoginModel();
 19             return View(model);
 20         }
 21 
 22         public IActionResult Index()
 23         {
 24             return View(CurrentAccountData());
 25         }
 26 
 27         [HttpPost]
 28         [ValidateAntiForgeryToken]
 29         [AllowAnonymous]
 30         public async Task<IActionResult> Login(LoginModel loginInfo, string returnUrl)
 31         {
 32             if (ModelState.IsValid)
 33             {
 34                 Admin admin = new Admin();
 35                 switch (loginInfo.LoginType)
 36                 {
 37                     case LoginType.UserName:
 38                         admin = await _context.Admins.FirstOrDefaultAsync(a => a.UserName == loginInfo.Account);
 39                         break;
 40                     case LoginType.Email:
 41                         admin = await _context.Admins.FirstOrDefaultAsync(a => a.Email == loginInfo.Account);
 42                         break;
 43                     case LoginType.Phone:
 44                         admin = await _context.Admins.FirstOrDefaultAsync(a => a.PhoneNumber == loginInfo.Account);
 45                         break;
 46                     default:
 47                         admin = null;
 48                         break;
 49                 }
 50 
 51                 if (admin != null)
 52                 {
 53                     string encryptedPassword = Encryptor.MD5Encrypt32(loginInfo.Password);
 54                     if (admin.Password == encryptedPassword)
 55                     {
 56                         ClaimsIdentity identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
 57                         identity.AddClaims(new[]
 58                         {
 59                             new Claim(ClaimTypes.Name, admin.UserName),
 60                             new Claim(ClaimTypes.Email,admin.Email),
 61                             new Claim(ClaimTypes.MobilePhone,admin.PhoneNumber),
 62                             new Claim(ClaimTypes.Role,"admin"),
 63                         });
 64                         var principal = new ClaimsPrincipal(identity);
 65                         await HttpContext.SignInAsync(principal,new AuthenticationProperties()
 66                         {
 67                             ExpiresUtc = DateTime.UtcNow.AddSeconds(8)
 68                         });
 69 
 70                         if (returnUrl != null)
 71                         {
 72                             return Redirect(returnUrl);
 73                         }
 74 
 75                         return RedirectToAction("Index");
 76                     }
 77                 }
 78                 ModelState.AddModelError("", "賬號或密碼錯誤");
 79                 return View(loginInfo);
 80             }
 81 
 82             return View(loginInfo);
 83         }
 84 
 85         [Authorize]
 86         public async Task<IActionResult> Logout()
 87         {
 88             await HttpContext.SignOutAsync();
 89             return View("Login");
 90         }
 91 
 92         Dictionary<string, object> CurrentAccountData()
 93         {
 94             var userName = HttpContext.User.Identity.Name;
 95             var user = _context.Admins.FirstOrDefault(a => a.UserName == userName);
 96 
 97             return new Dictionary<string, object>()
 98             {
 99                 ["使用者名稱"] = user.UserName,               
100                 ["郵箱"] = user.Email,
101                 ["手機號"] = user.PhoneNumber,
102             };
103         }
104     }

 

  由於 Login 檢視和 StudentAccountController 的 Login 檢視大致一致,因此可以將重複的部分提取出來作為一個分部檢視,在 Views/Shared 資料夾中建立分部檢視:

 

 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"/>

 

  對 StudentAccountController 的 Login 檢視做出修改:

 1     @model LoginModel
 2 
 3     @{
 4         ViewData["Title"] = "Login";
 5     }
 6 
 7     <h2>Login</h2>
 8     <br/>
 9     <div class="text-danger" asp-validation-summary="All"></div>
10     <br/>
11     <form asp-action="Login" method="post">
12         @await  Html.PartialAsync("_LoginPartialView",Model)
13     </form>

 

  設定 AdminAccount 的 Login 檢視:

 1     @model LoginModel
 2     @{
 3         ViewData["Title"] = "AdminIndex";
 4     }
 5 
 6     <h2>Login</h2>
 7     <br />
 8     <div class="text-danger" asp-validation-summary="All"></div>
 9     <br />
10     <form asp-action="Login" method="post">
11         @await Html.PartialAsync("_LoginPartialView", Model)
12     </form>

 

  AdminAccount 的 Index 檢視:

 1     @model Dictionary<string,object>
 2     @{
 3         ViewData["Title"] = "AccountInfo";
 4     }
 5 
 6     <h2>AccountInfo</h2>
 7 
 8     <ul>
 9         @foreach (var info in Model)
10         {
11             <li>@info.Key: @Model[info.Key]</li>
12         }
13         
14     </ul>

 

 

 

五、基於 Role 的 Identity 授權

  在此把之前所有與 Admin 有關的內容全部註釋掉或刪除,初始化身份為 admin 的使用者。

  修改 StudentInitial 類,新增名為 admin 的學生陣列並使用 AddToRoleAsync 為使用者新增身份。在新增 Role 之前需要在 RoleManager 物件中使用 Create 方法為 Role 資料庫新增特定的 Role 欄位:

 1     public class StudentInitiator
 2     {
 3         public static async Task InitialStudents(IServiceProvider serviceProvider)
 4         {
 5             UserManager<Student> userManager = serviceProvider.GetRequiredService<UserManager<Student>>();
 6             RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
 7             if (userManager.Users.Any())
 8             {
 9                 return;
10             }
11 
12             if (await roleManager.FindByNameAsync("Admin")==null)
13             {
14                 await roleManager.CreateAsync(new IdentityRole("Admin"));
15             }
16 
17             if (await roleManager.FindByNameAsync("Student")==null)
18             {
19                 await roleManager.CreateAsync(new IdentityRole("Student"));
20             }
21 
22             IEnumerable<Student> initialStudents = new[]
23             {
24                 new Student()
25                 {
26                     UserName = "U201600001",
27                     Name = "Nanase",
28                     Email = "[email protected]",
29                     PhoneNumber = "12345678910",
30                     Degree = Degrees.CollegeStudent,
31                     MaxBooksNumber = 10,
32                 },
33                 new Student()
34                 {
35                     UserName = "U201600002",
36                     Name = "Ruri",
37                     Email = "[email protected]",
38                     PhoneNumber = "12345678911",
39                     Degree = Degrees.DoctorateDegree,
40                     MaxBooksNumber = 15
41                 }
42             };
43 
44             IEnumerable<Student> initialAdmins = new[]
45             {
46                 new Student()
47                 {
48                     UserName = "A000000000",
49                     Name="Admin0000",
50                     Email = "[email protected]",
51                     PhoneNumber = "12345678912",
52                     Degree = Degrees.CollegeStudent,
53                     MaxBooksNumber = 20
54                 }
55             };
56             foreach (var student in initialStudents)
57             {
58                 await userManager.CreateAsync(student, student.UserName.Substring(student.UserName.Length - 6, 6));
59             }
60             foreach (var admin in initialAdmins)
61             {
62                 await userManager.CreateAsync(admin, "zxcZXC!123");
63                 await userManager.AddToRoleAsync(admin, "Admin");                
64             }
65         }
66     }

 

  然後新建一個 Admin 控制器,設定 [Authorize] 特性並指定 Role 屬性,使帶有特定 Role 的身份才可以訪問該控制器。

 1     [Authorize(Roles = "Admin")]
 2     public class AdminAccountController : Controller
 3     {
 4         private UserManager<Student> _userManager;
 5         private SignInManager<Student> _signInManager;
 6 
 7         public AdminAccountController(UserManager<Student> studentManager, SignInManager<Student> signInManager)
 8         {
 9             _userManager = studentManager;
10             _signInManager = signInManager;
11         }
12 
13         public IActionResult Index()
14         {
15             return View(CurrentAccountData());
16         }
17 
18 
19 
20         Dictionary<string, object> CurrentAccountData()
21         {
22             var userName = HttpContext.User.Identity.Name;
23             var user = _userManager.FindByNameAsync(userName).Result;
24 
25             return new Dictionary<string, object>()
26             {
27                 ["學號"] = userName,
28                 ["姓名"] = user.Name,
29                 ["郵箱"] = user.Email,
30                 ["手機號"] = user.PhoneNumber,
31             };
32         }
33     }

 

  使用 Role 不是 Admin 的賬戶登入:

 

 

   使用 Role 為 Admin 的賬戶登入:

 

   對 ConfigureServices 作進一步配置,新增 Cookie 的過期時間和不滿足 Authorize 條件時返回的 Url:

            services.ConfigureApplicationCookie(opts =>
            {
                opts.Cookie.HttpOnly = true;
                opts.LoginPath = "/StudentAccount/Login";
                opts.AccessDeniedPath = "/StudentAccount/Login";
                opts.ExpireTimeSpan=TimeSpan.FromMinutes(5);
            });

   則當 Role 不為 Admin 時將返回 /StudentAccount/Login 而非預設的 /Account/AccessDeny。