現行網頁幾乎都有登入功能,以前阿猩做的專案是靠自己造輪子,手動完成很多的工作,今天就來試一下.NET的Identity,同時與之前的做法比較一下,盡可能的了解Identity提供的功能跟限制有哪些。
Register(註冊)、Authentication(認證)與Authorization(授權)
使用者的資料
現在的Web應用程式大多都會搜集網站使用者的資料,就可以累積使用者資料,提供更適合的產品給客戶。要搜集使用者的資料,最基本的就是要提供註冊的功能,確保每個使用者資訊是獨立的,註冊的資料,就可以用於認證、授權,來實現更廣泛的應用。
認證跟授權兩者英文非常像,常被放在一起談,但也容易混淆。使用者會希望能盡可能地保護自己的私人資訊,在網路的世界中,要如何「認證」當下的使用者是誰,就變得非常重要,登入的功能就是實現認證的方法。透過認證,我們才可以「授權」給使用者,針對不同使用者提供不一樣的服務。甚至有些使用狀況是授權給角色,例如老闆可以看到員工的薪水,但員工不可以看到其他同事的薪水。
註冊系統
實務上,通常會將使用者資訊儲存於資料庫,但對於沒有經驗的開發者而言,如何建立合適的表單可能都是問題,更別說現在還有第三方登入的機制。因此ASP.NET Identity提供了一個不錯的流程,幫助我們完成註冊所需的功能,並提供一定程度的可調整性。
ASP.NET Identity初體驗
透過一行指令,我們就能建立出具有ASP.NET Identity的專案。例如:
dotnet new mvc --auth Individual -uld -o IdentityTest
超級方便的,除了註冊、連登入功能及畫面都幫我們做好了,只要按下Apply Migrations,就直接在資料庫中建好所需的Table了(圖1)!
但要是這麼做就失去寫這篇網誌的本意了,阿猩今天就要從0開始,希望能從實作的過程中,學習更多的概念。
實作Identity完成Register功能
安裝套件
在.NET Core的環境中,可以到nuget(參1)下載並安裝需要的套件。
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 6.0.1
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.1
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 6.0.1
使用IdentityUser
IdentityUser類別是一個.NET建立好的欄位(參2),用於儲存使用者註冊訊息,例如使用者名稱、信箱。
1、定義Table Schema
如果需要新增其他欄位,就建立一個類別後,再繼承IdentityUser就好。例如
using Microsoft.AspNetCore.Identity;
namespace IdentityTest.Models;
public class ApplicationUser : IdentityUser
{
public string City { get; set; }
public string xxxx { get; set; }
}
2、繼承IdentityDbContext
定義了Table Schema,當然要建立資料到資料庫。這裡是使用EntityFramework,果對於EntityFramework還不熟,可參考阿猩之前寫的文章(參3)。
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
3、建立連線字串並註冊服務
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
. AddEntityFrameworkStores<ApplicationDbContext>();
4、在資料庫中建立表單
執行migrations後會發現專案中多了Migrations的資料夾,裡面會新增一個cs檔案,內容物為建立Table Schema的程式碼,包含欄位名稱、型別、是否可null等。
dotnet ef migrations add InitialCreate
執行update時會讀取最新的migrations.cs並建立Table
dotnet ef database update
阿猩在執行migrations時遇到一些問題,如果你也失敗了,請參考這篇文章(參4)。如果想修改Identity對於資料庫產生的Table名稱,除了建立後手動修改,或許在Migration檔案中修改也是種方式,另外搭配DbContext的設定(參5)。
在MVC專案中完成註冊功能
資料庫Table建好了之後,接著是完成前端的工作,就是View的顯示。以及後端的工作,阿猩先寫在Controller。阿猩是用.NET Core MVC作為範例,故後續View的部分都會用Razor的語法來寫。
1、我們先在Home底下的Index.cshtml中加入
<a asp-action="Register" asp-controller="Account">Register</a>
2、在/Controllers/中建立AccountController.cs,並寫好Action
public IActionResult Register()
{
return View();
}
3、MVC預設View的讀取路徑為Controller名稱,所以我們需要在/Views/Account/中建立Register.cshtml
<form asp-controller="Account" asp-action="Register">
<div asp-validation-summary="All">
<div>
<label asp-for="UserName"></label>
<input asp-for="UserName" type="text">
</div>
<div>
<label asp-for="Password"></label>
<input asp-for="Password" type="password">
</div>
….省略
<div>
<input type="submit" value="Register">
</div>
</div>
</form>
4、建立ViewModel
並將前端資料寫入資料庫資料庫是最後的防線,如果錯誤或不符合標準的資料被放進去資料庫,日後要處理就頭大囉。ViewModel的目的就是為了要驗證前端送進來的資料,如果不符合規定,就不執行Action囉。透過Attribution可以設定完成一些驗證工作(參6),例如帳號要有幾個字元、密碼長度、是否包含大小寫、特殊符號等等,除了後端驗證之外,ViewModel也可以透過ErrorMessage提供前端錯誤訊息。
5、將前端資料寫進資料庫
首先要DI UserManager服務,用於將使用者寫進資料庫。此範例的寫法為,如果登入成功,就跳轉至登入畫面。
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser()
{
UserName = model.UserName,
Email = model.Email,
City = model.City,
xxxx = model.xxxx
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(Login));
}
}
return View(model);
}
IdentityUser細節設定
建立使用者資料除了上述的ViewModel之外,Identity也提供一系列的可調整性,像是密碼的屬性、是否進行信箱驗證等。更強大的是,除了針對使用者資料,還提供了其他例如Password Hash迭代次數次、Cookie的設定等等,有興趣可參考此篇(參7)
完成Login功能
登入概念須知
現行登入方式可分為帳密登入及第三方登入,第三方登入已經超出範圍太多,不在今天的內容裡,一般登入對於後端的流程簡單來說是,
1、輸入帳號密碼
2、到資料庫驗證
3、驗證成功後發送有唯一性的識別碼給使用者,並儲存在前端
如果密碼直接明碼存在資料庫,那公司員工不就可以冒充使用者進行操作,那阿猩要去銀行工作!所以密碼儲存之前,通常會經過加密或雜湊的處理,更嚴謹一點可能會加鹽後再作處裡,有興趣的話可自行Google Bcrypt。ASP.NET Identity是使用PBKDF2,如果想換別的加密或雜湊,也許也得自己手刻了(參8)
使用SignInManager
透過SignInManager.PasswordSignInAsync可實現登入的功能,SignInManager.PasswordSignOutAsync則可實現登出的功能。阿猩查了一下,ASP.NET Identity有提供常見的JWT Token可作為使用者身分驗證的方法(參9、參10),JWT的實現方法也不難理解,前一份專案阿猩就是手刻。JWT Token主要包含3個元素
1、Header:指定使用的演算法
2、Payload:用戶基礎資料
3、加上私有的Key、過期時間等後進行雜湊
4、最後使用Base64加密
Payload中不會放機密的資料,例如密碼,因為Base64是可逆的,而私有Key也要好好保存,如果外流了Token被偽造的機率就變大囉。
ASP.NET Identity預設是將Token儲存於Cookie,如果想換成Session或LocalStorge阿猩好像沒有找到說明的技術文章,也許得自己手刻了(參11、參12)。
第三方登入
阿猩爬了一下文,ASP.NET Identity看起來應該也可以應用於第三方登入,但沒實作過不敢亂說,日後有機會再來試試看(參13)
今天就講到這裡啦,授權下一篇再來講~
參考:
1、https://www.nuget.org/
2、https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.aspnetcore.identity.entityframeworkcore.identityuser?view=aspnetcore-1.1
3、https://dotblogs.com.tw/supergary/2022/02/06/EntityFramework
4、https://blog.darkthread.net/blog/dotnet-ef-not-found
5、https://blog.yowko.com/aspnet-identity-db-first-mssql
6、https://docs.microsoft.com/zh-tw/aspnet/core/mvc/models/validation?view=aspnetcore-6.0
7、https://www.uj5u.com/net/253750.html
8、https://bbs.csdn.net/topics/396808847
9、https://dejanstojanovic.net/aspnet/2018/june/token-based-authentication-in-aspnet-core-part-2/
10、https://blog.cashwu.com/blog/asp-net-core-jwt-authentication/
11、https://blog.csdn.net/tmchongye/article/details/63983856
12、https://bbs.csdn.net/topics/396808847
13、https://dotblogs.com.tw/kinanson/2015/04/29/151174