前言:git
本系列文章主要爲我以前所學知識的一次微小的實踐,以我學校圖書館管理系統爲雛形所做。github
本系列文章主要參考資料:數據庫
微軟文檔:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windowswindows
《Pro ASP.NET MVC 5》、《鋒利的 jQuery》數組
當此系列文章寫完後會在一週內推出修正版。安全
此係列皆使用 VS2017+C# 做爲開發環境。若是有什麼問題或者意見歡迎在留言區進行留言。 app
項目 github 地址:https://github.com/NanaseRuri/LibraryDemo框架
本章內容:Identity 框架的配置、對帳戶進行受權的配置、數據庫的初始化方法、自定義 TagHelperasync
一到四爲對 Student 即 Identity框架的使用,第五節爲對 Admin 用戶的配置ide
1、自定義帳號和密碼的限制
在 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 方法限制用戶名可以使用的字符,須要單獨輸入每一個字符。
剩下的設置分別爲限制密碼必須有符號 / 包含小寫字母 / 包含大寫字母 / 包含數字。
2、對數據庫進行初始化
在此建立一個 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 = "Nanase@cnblog.com", 15 PhoneNumber = "12345678910", 16 Degree = Degrees.CollegeStudent, 17 MaxBooksNumber = 10, 18 }, 19 new Student() 20 { 21 UserName = "U201600002", 22 Name = "Ruri", 23 Email = "NanaseRuri@cnblog.com", 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 中對帳號和密碼的限制。
此處咱們使用帳號的後六位做爲密碼。啓動網頁後查看數據庫的數據:
3、創建驗證所用的控制器以及視圖
首先建立一個視圖模型用於存儲帳號的信息,爲了方便實現多種登陸方式,此處建立一個 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 頁面:
4、建立登出網頁
簡單地調用 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 = "admin@cnblog.com", 17 PhoneNumber = "10000000000", 18 Password = "123456" 19 }, 20 new Admin() 21 { 22 UserName = "admin1", 23 Email = "admin1@cnblog.com", 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>
5、基於 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 = "Nanase@cnblog.com", 29 PhoneNumber = "12345678910", 30 Degree = Degrees.CollegeStudent, 31 MaxBooksNumber = 10, 32 }, 33 new Student() 34 { 35 UserName = "U201600002", 36 Name = "Ruri", 37 Email = "NanaseRuri@cnblog.com", 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 = "Admin@cnblog.com", 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。