我們經常使用的各類網站和App均會涉及注冊、登錄和修改密碼等功能,登錄系統后,有些功能會提示沒有權限,甚至有些位置我們無法訪問,這些都是系統權限和認證的體現。
我們從本章及后面的章節中,將學習在ASP.NET Core應用程序中使用ASP.NET Core Identity實現安全認證相關功能所需要掌握的知識。
本章主要向讀者介紹如下內容。
- 什么是ASP.NET Core Identity。
- 如何在系統中啟用Identity服務。
- UserManager與SignInManager的API介紹及使用。
- 登錄用戶的Cookie管理。
21.1 ASP.NET Core Identity介紹
ASP.NET Core Identity是一個會員身份系統,早期它的名字是Membership,當然那是一段“古老”的歷史,現在我們來了解全新的Identity。它允許我們創建、讀取、更新和刪除賬戶。支持賬號驗證、身份驗證、授權、恢復密碼和SMS雙因子身份驗證。它還支持微軟、Facebook和google等第三方登錄提供商。它提供了一個豐富的API,并且這些API還可以進行大量的擴展。我們將在本書的后面實現這些功能。
添加ASP.NET Core Identity服務
這里采用的是EF Core,因為要讓我們的系統支持Identity服務,所以需要安裝它的程序包。打開NuGet管理器,安裝Microsoft.AspNetCore.Identity.EntityFrameworkCore即可。
以下是添加和配置ASP.NET Core Identity服務的步驟。
使AppDbContext繼承類IdentityDbContext,然后引入命名空間,代碼如下。
public class AppDbContext:IdentityDbContext
{
//其余代碼
}
- 應用程序AppDbContextDbContext類必須繼承IdentityDbContext類而不是DbContext類。
- 因為IdentityDbContext提供了管理SQL Server中的Identity表所需的所有DbSet屬性,所以將看到ASP.NET Core Identity框架中要生成的所有數據庫表。
- 如果瀏覽IdentityDbContext類的定義(按F12鍵可以看到),則將看到它繼承自DbContext類。因此,如果類繼承自IdentityDbContext類,那么不必顯式繼承DbContext類。
配置ASP.NET Core Identity服務。在Startup類的ConfigureServices()方法中,添加以下代碼行。
services.AddIdentity<IdentityUser,IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
- AddIdentity()方法是指為系統提供默認的用戶和角色類型的身份驗證系統。
- IdentityUser類由ASP.NET Core提供,包含UserName、PasswordHash和Email等屬性。這是ASP.NET Core Identity框架默認使用的類,用于管理應用程序的注冊用戶。
- 如果讀者希望存儲有關注冊用戶的其他信息,比如性別、城市等,則需要創建一個派生自IdentityUser的自定義類。在此自定義類中添加所需的其他屬性,然后插入此類而不是內置的IdentityUser類。我們將在后面的章節中學習如何執行此操作。
- 同樣,IdentityRole也是ASP.NET Core Identity提供的內置類,包含角色信息。
- 使用EntityFrameWork Core從基礎SQL Server數據庫存儲和查詢注冊用戶的角色信息。
- 使用AddEntityFrameworkStores()方法,然后指定DbContext類作為泛型參數。
接下來,將Authentication()中間件添加到請求管道,代碼如下。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果環境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//否則顯示用戶友好的錯誤頁面
else if(env.IsStaging() || env.IsProduction() || env.IsEnvironment("UAT"))
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
//使用純靜態文件支持的中間件,而不使用帶有終端的中間件
app.UseStaticFiles();
//添加驗證中間件
app.UseAuthentication();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
在Startup類的Configure()方法中,調用UseAuthentication()方法將Authentication()中間件添加到應用程序的請求處理管道中。我們希望能夠在請求到達MVC中間件之前對用戶進行身份驗證。因此,在請求處理管道的UseRouting()中間件之前添加認證中間件。這很重要,因為我們之前講過中間件的添加順序不能亂。
現在開始添加身份遷移。在Visual Studio中的程序包控制臺窗口執行以下命令以添加新遷移。
Add-Migration AddingIdentity
此遷移包含用于創建ASP.NET Core Identity系統所需的表的代碼。
如果運行,則會出現以下錯誤。
The entity type'IdentityUserLogin'requires a primary key to be defined.
之前因為要封裝Seed()方法,所以重寫OnModelCreating()方法。出現這個錯誤是因為我們在DbContext類中重寫了OnModelCreating()方法,但未調用基本IdentityDbContext類OnModelCreating()方法。
Identity表的鍵映射在IdentityDbContext類的OnModelCreating()方法中。因此,要解決這個錯誤,需要做的是,調用基類OnModelCreating()使用該方法的基礎關鍵字,代碼如下。
public class AppDbContext:IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
{
}
public DbSet<Student> Students{get;set;}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Seed();
}
}
執行Update-Database命令以應用遷移記錄并創建所需的身份表,如圖21.1所示。
圖21.1
21.2 使用ASP.NET Core Identity注冊新用戶
現在已經創建好了表的信息,接下來我們增加一個注冊功能,讓用戶能夠注冊到系統中。
新用戶注冊視圖應如圖21.2所示。為了能夠注冊為新用戶,需要郵箱地址和密碼兩個字段。
圖21.2
21.2.1 RegisterViewModel視圖模型
我們將使用RegisterViewModel類作為Register視圖的模型,它負責將視圖中的信息傳遞給控制器。為了驗證信息是否正確,我們使用了幾個ASP.NET Core驗證屬性。在之前的章節中詳細說明過這些屬性和模型驗證。
using System.ComponentModel.DataAnnotations;
namespace MockSchoolManagement.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "郵箱地址")]
public string Email{get;set;}
[Required]
[DataType(DataType.Password)]
[Display(Name = "密碼")]
public string Password{get;set;}
[DataType(DataType.Password)]
[Display(Name = "確認密碼")]
[Compare("Password",
ErrorMessage = "密碼與確認密碼不一致,請重新輸入.")]
public string ConfirmPassword{get;set;}
}
}
在這里我們添加了DataType特性,它的主要作用是指定比數據庫內部類型更具體的數據類型。DataType枚舉提供了多種數據類型,比如日期、時間、電話號碼、貨幣和郵箱地址等。但是請注意,DataType特性不提供任何驗證,它主要服務于我們的視圖文件,比如,DataType.EmailAddress可以在視圖中創建mailto:鏈接,DataType.Date則會在支持html5的瀏覽器中提供日期選擇器。
21.2.2 賬戶控制器
賬戶控制器(AccountController)是指所有與賬戶相關的CRUD(增加、讀取、更新和刪除)操作都將在此控制器中。目前我們只有Register()操作方法,可以通過向/account/register發出GET請求來實現此操作方法。
using Microsoft.AspNetCore.Mvc;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
[HttpGet]
public IActionResult Register()
{
return View();
}
}
}
21.2.3 注冊視圖中的代碼
將此視圖放在Views/Account文件夾中,此視圖的模型是我們在前面創建的Register ViewModel。
@model RegisterViewModel
@{ViewBag.Title = "用戶注冊";}
<h1>用戶注冊</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"> </div>
<div class="form-group">
<label asp-for="Email"> </label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"> </span>
</div>
<div class="form-group">
<label asp-for="Password"> </label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"> </span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"> </label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"> </span>
</div>
<button type="submit" class="btn btn-primary">注冊</button>
</form>
</div>
</div>
21.2.4 添加注冊按鈕
在布局視圖中添加注冊按鈕,我們需要在_Layout.cshtml文件中找到ID為collapsibleNavbar的導航菜單欄,在下方添加注冊按鈕,導航到對應的視圖,代碼如下。
<div id="collapsibleNavbar" class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="Index">學生列表</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="Create">添加學生</a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="register"> 注冊 </a>
</li>
</ul>
</div>
運行項目后,單擊注冊按鈕即可看到圖21.2所示的效果圖,接下來我們實現處理HttpPOST請求到/account/register的Register()操作方法。然后通過表單Taghelpers將數據發布到ASP.NET Core Identity中創建賬戶。
21.3 UserManager和SignInManager服務
在本節我們學習使用ASP.NET Core Identity提供的UserManager服務創建新用戶,然后使用其提供的SignInManager服務來登錄用戶。
UserManager <IdentityUser>類包含管理基礎數據存儲中的用戶所需的方法。比如,此類具有CreateAsync()、DeleteAsync()和UpdateAsync()等方法來創建、刪除和更新用戶,如圖21.3所示。
圖21.3
SignInManager <IdentityUser>類包含用戶登錄所需的方法。比如,SignInManager類具有SignInAsync()、SignOutAsync()等方法來登錄和注銷用戶,如圖21.4所示。
- UserManager和SignInManager服務都需要使用構造函數注入AccountController,并且這兩個服務都接收泛型參數。
- 這些服務接收泛型參數的User類。目前,我們使用內置的IdentityUser類作為泛型參數的參數。
- 這兩個服務的通用參數User是一個擴展類。這意味著,我們可以自定義與用戶有關的信息和其他數據,來創建我們的自定義用戶。
圖21.4
- 我們可以聲明自己的自定義類作為泛型參數,而不是內置的IdentityUser類。
以下是AccountController的完整代碼。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
private UserManager<IdentityUser> _userManager;
private SignInManager<IdentityUser> _signInManager;
public AccountController(UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
this._userManager = userManager;
this._signInManager = signInManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if(ModelState.IsValid)
{
//將數據從RegisterViewModel復制到IdentityUser
var user = new IdentityUser
{
UserName = model.Email,
Email = model.Email
};
//將用戶數據存儲在AspNetUsers數據庫表中
var result = await _userManager.CreateAsync(user,model.Password);
//如果成功創建用戶,則使用登錄服務登錄用戶信息
//并重定向到HomeController的索引操作
if(result.Succeeded)
{
await _signInManager.SignInAsync(user,isPersistent:false);
return RedirectToAction("index","home");
}
//如果有任何錯誤,則將它們添加到ModelState對象中
//將由驗證摘要標記助手顯示到視圖中
foreach(var error in result.Errors)
{
ModelState.AddModelError(string.Empty,error.Description);
}
}
return View(model);
}
}
}
此時,如果讀者運行項目并提供有效的郵箱地址和密碼,則它會在SQL Server數據庫的AspNetUsers表中創建賬戶。讀者可以從Visual Studio的SQL Server對象資源管理器中查看此數據,如圖21.5所示。
圖21.5
21.3.1 ASP.NET Core Identity中對密碼復雜度的處理
在剛剛注冊的時候,我們發現有兩個問題。
- 密碼驗證機制太復雜了。
- 它是英文的,對于我們來說支持不是很友好。
這是因為ASP.NET Core IdentityOptions類在ASP.NET Core中用于配置密碼復雜性規則。默認情況下,ASP.NET Core身份不允許創建簡單的密碼來保護我們的應用程序免受自動暴力攻擊。
當我們嘗試使用像abc這樣的簡單密碼注冊新賬戶時,會顯示創建失敗,讀者將看到如圖21.6所示的驗證錯誤。
圖21.6
我們在圖21.6中看到中文提示,后面的章節會告訴讀者如何配置。
21.3.2 ASP.NET Core Identity密碼默認設置
在ASP.NET Core Identity中,密碼默認設置在PasswordOptions類中。讀者可以在ASP.NET Core GitHub倉庫中找到此類的源代碼。只需在倉庫中搜索PasswordOptions類。
代碼如下。
public class PasswordOptions
{
public int RequiredLength{get;set;} = 6;
public int RequiredUniqueChars{get;set;} = 1;
public bool RequireNonAlphanumeric{get;set;} = true;
public bool RequireLowercase{get;set;} = true;
public bool RequireUppercase{get;set;} = true;
public bool RequireDigit{get;set;} = true;
}
相關參數的說明如表21.1(略)所示。
21.3.3 覆蓋ASP.NET Core身份中的密碼默認設置
我們可以通過在Startup類的ConfigureServices()方法中使用IServiceCollection接口的Configure()方法來實現這一點。
services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 3;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
});
也可以在添加身份服務時執行此操作,代碼如下。
services.AddIdentity<IdentityUser,IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 3;
options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>();
當然,在這里推薦使用IdentityOptions的形式進行配置,因為它可以作為一個獨立服務,而不是嵌套在AddIdentity()方法中。
IdentityOptions對象中除了Password的配置信息,還有用戶、登錄、策略等配置信息,我們可以根據不同的場景進行靈活的配置。
- UserOptions。
- SignInOptions。
- LockoutOptions。
- TokenOptions。
- StoreOptions。
- ClaimsIdentityOptions。
21.3.4 修改中文提示的錯誤信息
Identity提供了AddErrorDescriber()方法,可方便我們進行錯誤內容的配置和處理。
ASP.NET Core默認提供的都是英文提示,我們可以將它們修改為中文。現在我們創建一個CustomIdentityErrorDescriber的類文件,路徑為根目錄下創建的CustomerMiddlewares文件夾,然后繼承IdentityErrorDescriber服務,添加以下代碼。
public class CustomIdentityErrorDescriber:IdentityErrorDescriber
{
public override IdentityError DefaultError()
{
return new IdentityError{Code = nameof(DefaultError),Description = $"發生了未知的故障。" };
}
public override IdentityError ConcurrencyFailure()
{
return new IdentityError{Code = nameof(ConcurrencyFailure),Description = "樂觀并發失敗,對象已被修改。" };
}
public override IdentityError PasswordMismatch()
{
return new IdentityError{Code = nameof(PasswordMismatch),Description = "密碼錯誤" };
}
public override IdentityError InvalidToken()
{
return new IdentityError{Code = nameof(InvalidToken),Description = "無效的令牌." };
}
public override IdentityError LoginAlreadyAssociated()
{
return new IdentityError{Code = nameof(LoginAlreadyAssociated),Description = "具有此登錄的用戶已經存在." };
}
public override IdentityError InvalidUserName(string userName)
{
return new IdentityError{Code = nameof(InvalidUserName),Description = $"用戶名'{userName}'無效,只能包含字母或數字." };
}
public override IdentityError InvalidEmail(string email)
{
return new IdentityError{Code = nameof(InvalidEmail),Description = $"郵箱'{email}'無效." };
}
public override IdentityError DuplicateUserName(string userName)
{
return new IdentityError{Code = nameof(DuplicateUserName),Description = $"用戶名'{userName}'已被使用." };
}
public override IdentityError DuplicateEmail(string email)
{
return new IdentityError{Code = nameof(DuplicateEmail),Description = $"郵箱'{email}'已被使用." };
}
public override IdentityError InvalidRoleName(string role)
{
return new IdentityError{Code = nameof(InvalidRoleName),Description = $"角色名'{role}'無效." };
}
public override IdentityError DuplicateRoleName(string role)
{
return new IdentityError{Code = nameof(DuplicateRoleName),Description = $"角色名'{role}'已被使用." };
}
public override IdentityError UserAlreadyHasPassword()
{
return new IdentityError{Code = nameof(UserAlreadyHasPassword),Description = "該用戶已設置了密碼." };
}
public override IdentityError UserLockoutNotEnabled()
{
return new IdentityError{Code = nameof(UserLockoutNotEnabled),Description = "此用戶未啟用鎖定." };
}
public override IdentityError UserAlreadyInRole(string role)
{
return new IdentityError{Code = nameof(UserAlreadyInRole),Description = $"用戶已關聯角色'{role}'." };
}
public override IdentityError UserNotInRole(string role)
{
return new IdentityError{Code = nameof(UserNotInRole),Description = $"用戶未關聯角色'{role}'." };
}
public override IdentityError PasswordTooShort(int length)
{
return new IdentityError{Code = nameof(PasswordTooShort),Description = $"密碼必須至少是{length}字符." };
}
public override IdentityError PasswordRequiresNonAlphanumeric()
{
return new IdentityError
{
Code = nameof(PasswordRequiresNonAlphanumeric),
Description = "密碼必須至少有一個非字母數字字符."
};
}
public override IdentityError PasswordRequiresDigit()
{
return new IdentityError{Code = nameof(PasswordRequiresDigit),Description = $"密碼必須至少有一個數字('0'-'9')." };
}
public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
{
return new IdentityError{Code = nameof(PasswordRequiresUniqueChars),Description = $"密碼必須使用至少不同的{uniqueChars}字符。" };
}
public override IdentityError PasswordRequiresLower()
{
return new IdentityError{Code = nameof(PasswordRequiresLower),Description = "密碼必須至少有一個小寫字母('a'-'z')." };
}
public override IdentityError PasswordRequiresUpper()
{
return new IdentityError{Code = nameof(PasswordRequiresUpper),Description = "密碼必須至少有一個大寫字母('A'-'Z')." };
}
}
回到Startup類的ConfigureServices()方法中,在AddIdentity()服務中使用AddErrorDescriber()方法覆蓋默認的錯誤提示內容,代碼如下。
services.AddIdentity<IdentityUser,IdentityRole>().AddErrorDescriber<CustomIdentityErrorDescriber>().AddEntityFrameworkStores<AppDbContext>();
配置完成之后,提示變為中文,注冊時密碼長度達到6位即可。
21.4 登錄狀態及注銷功能的實現
在本節中我們學習如何判斷用戶是否登錄,以及注冊、登錄和注銷等功能是否可實現。
首先來看一看如何在ASP.NET Core中實現注銷功能。如果用戶未登錄,則顯示登錄和注冊按鈕,如圖21.7所示。
圖21.7
如果用戶已登錄,請隱藏登錄和注冊按鈕并顯示注銷按鈕,如圖21.8所示。
圖21.8
我們需要在_Layout.cshtml文件中找到ID為collapsibleNavbar的導航菜單欄,修改代碼如下。
在下方代碼中注入了SignInManager,以便我們檢查用戶是否已登錄,來決定顯示和隱藏的內容。
@using Microsoft.AspNetCore.Identity @inject SignInManager<IdentityUser>
_signInManager
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="index">學生列表</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="create">添加學生</a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
@*如果用戶已登錄,則顯示注銷鏈接*@ @if(_signInManager.IsSignedIn(User)) {
<li class="nav-item">
<form method="post" asp-controller="account" asp-action="logout">
<button type="submit" style="width:auto"
class="nav-link btn btn-link py-0">
注銷 @User.Identity.Name
</button>
</form>
</li>
}else{
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="register">
注冊
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="login">
登錄
</a>
</li>
}
</ul>
</div>
</IdentityUser>
然后在AccountController中添加以下Logout()方法。
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("index","home");
}
請注意,我們使用POST請求將用戶注銷,而不使用GET請求,因為該方法可能會被濫用。惡意者可能會誘騙用戶單擊某張圖片,將圖片的src屬性設置為應用程序注銷URL,這樣會造成用戶在不知不覺中退出了賬戶。
21.5 ASP.NET Core Identity中的登錄功能實現
在本節中,我們將討論使用ASP.NET Core Identity的API在ASP.NET Core應用程序中實現登錄功能。要在ASP.NET Core應用程序中實現登錄功能,我們需要實現以下功能。
- 登錄視圖模型。
- 登錄視圖。
- AccountController中的兩個Login()操作方法。
21.5.1 LoginViewModel登錄視圖模型
要在系統中登錄用戶,則需要其郵箱、用戶名、密碼以及使其選擇是否需要持久性Cookie或會話Cookie。
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email{get;set;}
[Required]
[DataType(DataType.Password)]
public string Password{get;set;}
[Display(Name = "記住我")]
public bool RememberMe{get;set;}
}
21.5.2 登錄視圖的代碼
登錄視圖的代碼如下。
@model LoginViewModel
@{ViewBag.Title = "用戶登錄";}
<h1>用戶登錄</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"> </div>
<div class="form-group">
<label asp-for="Email"> </label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"> </span>
</div>
<div class="form-group">
<label asp-for="Password"> </label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"> </span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">登錄</button>
</form>
</div>
</div>
21.5.3 AccountController中的Login()操作方法
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
private UserManager<IdentityUser> _userManager;
private SignInManager<IdentityUser> _signInManager;
public AccountController(UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
this._userManager = userManager;
this._signInManager = signInManager;
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if(ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
model.Email,model.Password,model.RememberMe,false);
if(result.Succeeded)
{
return RedirectToAction("index","home");
}
ModelState.AddModelError(string.Empty,"登錄失敗,請重試");
}
return View(model);
}
}
}
21.5.4 會話Cookie與持久性Cookie
維基百科解釋:Cookie并不是它的原意“甜餅”的意思,而是一個保存在客戶機中的簡單的文本文件,這個文件與特定的Web文檔關聯在一起,保存了該客戶機訪問這個Web文檔時的信息,當客戶機再次訪問這個Web文檔時這些信息可供該文檔使用。由于“Cookie”具有可以保存在客戶機上的神奇特性,因此它可以幫助我們實現記錄用戶個人信息的功能,而這一切都不必使用復雜的CGI等程序。
簡單來說,我們把Cookie理解為一個大小不超過4kB,便于我們在客戶端保存一些用戶個人信息的功能。
在ASP.NET Core Identity中,用戶成功登錄后,將發出Cookie,并將此Cookie隨每個請求一起發送到服務器,服務器會解析此Cookie信息來了解用戶是否已經通過身份驗證和登錄。此Cookie可以是會話Cookie或持久Cookie。
會話Cookie是指用戶登錄成功后,Cookie會被創建并存儲在瀏覽器會話實例中。會話Cookie不包含過期時間,它會在瀏覽器窗口關閉時被永久刪除。
持久Cookie是指用戶登錄成功后,Cookie會被創建并存儲在瀏覽器中,因為是持久Cookie,所以在關閉瀏覽器窗口后,它不會被刪除。但是,它通常有一個到期時間,會在到期后被刪除。
在LoginViewModel.cs視圖模型中,我們已經添加了一個bool類型的RememberMe屬性。用戶可在登錄時選擇記住我,選中即使用持久性Cookie,而未選中則為會話Cookie。
現在運行項目,我們可以在登錄的時候選擇記住我,登錄成功后如圖21.9所示。
圖21.9
打開開發者工具(按F12鍵),觀察圖21.9框中的內容,可以發現過期時間是很長的。現在關閉瀏覽器,并將其再次打開,用戶也依然是登錄狀態。這便是持久性Cookie的作用,只有在到期時間到了之后才會刪除。
至于會話Cookie驗證,我們在登錄的時候取消選擇記住我,然后看到如圖21.10所示的內容。
圖21.10
這里已經是一個會話了,它不包含過期時間,在關閉瀏覽器后,再次將其打開,系統會自動注銷用戶。
以上就是持久性Cookie與會話Cookie的區別了。
在本章中我們學習了Identity的基本功能,創建一個系統用戶并完成了登錄注冊及狀態檢查。在后面的章節中,內容會逐步深入,可配合源代碼學習。
21.6 小結
本章介紹了ASP.NET Core Identity框架的定位及作用,并利用它提供的API完成了用戶的登錄與注銷等基本功能。在后面的章節中我們會使用更多的API將系統趨于完善。
本文摘自《深入淺出 ASP.NET Core》