本篇筆記紀錄使用 Net Core Identity 產生 Token 所做令牌做一些驗證信處理,帳號登入錯誤太多次,我們可以設定安全規則,在多次登入失敗可以封鎖幾分鐘帳號。我們可以產生令牌,令牌是有時效的,超過時間就會過期,這邊令牌非一般 JWT ,他是有 Data Protection API 做一層加密,這邊不會探討這部分。
阻止帳號未驗證信箱登入
AspNetUsers
有一個 EmailConfirmed
可以確認是否有驗證信箱。使用前需要在 Startup.cs
的ConfigureServices()
中的 RequireConfirmedEmail
屬性設定為true
。
1
2
3
4
|
services.Configure<IdentityOptions>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
});
|
1
2
3
4
5
6
7
|
// 沒有這一個判斷會走到「登入失敗,請重試」,注意這邊要做這個判斷,使用者才知道是什麼錯誤。
if (user != null & !user.EmailConfirmed &&
await _userManager.CheckPasswordAsync(user, model.Password))
{
ModelState.AddModelError(string.Empty, "你的信箱尚為進行驗證");
return View(model);
}
|
GIT: 使用者登入做信箱驗證動作 · malagege/NetCoreAuthSample@92872cc
電子信箱確認令牌
電子令牌需要使用_userManager.GenerateEmailConfirmationTokenAsync(user)
產生令牌(Token)。
這邊可以用Url.Action
產生Email驗證頁面連結。透過 _userManager.ConfirmEmailAsync(user,token)
進行驗證。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
public async Task<IActionResult> RegisterAsync(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
City = model.City,
};
var result = await _userManager.CreateAsync(user, model.Password); // 第二個參數密碼要放,沒放不會檢核密碼規則
if (result.Succeeded)
{
string confirmationLink = await GenerateConfirmactionLinkAsync(user);
//當前 admin 新增帳號,導回使用者清單
if(_signInManager.IsSignedIn(User) && User.IsInRole("Admin"))
{
return RedirectToAction("userlist", "admin");
}
ViewBag.ErrorTitle = "註冊成功";
ViewBag.ErrorMessage = "已經發送一組驗證信,請進行驗證。";
return View("Error");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
private async Task<string> GenerateConfirmactionLinkAsync(ApplicationUser user)
{
string token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
string confirmationLink = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, token = token }, Request.Scheme);
return confirmationLink;
}
|
這邊特別提一下,Url.Action("ConfirmEmail", "Account", new { userId = user.Id, token = token }, Request.Scheme);
裡面的Request.Scheme是什麼,正常這個參數是填寫http
或https
,但使用這個HttpRequest物件可以對應Server 設定。
產生 NotSupportedException: No IUserTwoFactorTokenProvider<TUser> named ‘Default’ is registered. 錯誤
需要在ConfigureServices()
方法添加AddDefaultTokenProviders()
,該方法可以用來信箱令牌驗證、密碼重製、雙因子驗證。
接下來就把 View 頁面用上去就完成
你可能會忽略程式錯誤但使用者新增上去
理論上我猜Identity 相關 function 裡面有做 SaveChange()
,所以我嘗試外面加 TransactionScope
就能解決這個問題。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public async Task<IActionResult> RegisterAsync(RegisterViewModel model)
{
using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
City = model.City,
};
var result = await _userManager.CreateAsync(user, model.Password); // 第二個參數密碼要放,沒放不會檢核密碼規則
if (result.Succeeded)
{
scope.Complete();
string confirmationLink = await GenerateConfirmactionLinkAsync(user);
_logger.LogInformation($"發送驗證信連結:{confirmationLink}");
//當前 admin 新增帳號,導回使用者清單
if(_signInManager.IsSignedIn(User) && User.IsInRole("Admin"))
{
return RedirectToAction("userlist", "admin");
}
ViewBag.ErrorTitle = "註冊成功";
ViewBag.ErrorMessage = "已經發送一組驗證信,請進行驗證。";
return View("Error");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
|
GIT: 申請帳號發送驗證信動作與檢查 · malagege/NetCoreAuthSample@3fbb828
第三方令牌做信箱驗證
跟上面邏輯一樣,由於這邊範例是信箱為key,通常第三方登入綁定原帳號不一定是用信箱,所以這邊就不實作了,不一定要做信箱驗證。
啟用帳號
過了超過驗證信箱時間,我們需要重新發送驗證信功能。更上面差不多事情就不細講,參照GITHUB設定。
GIT: 忘記密碼功能 · malagege/NetCoreAuthSample@f06b336
主要重點_userManager.GeneratePasswordResetTokenAsync
產生 token。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[HttpPost]
public async Task<IActionResult> ForgetPasswordAsync(EmailAddressViewModel model)
{
if (ModelState.IsValid)
{
// 用信箱查詢帳號
ApplicationUser user = await _userManager.FindByNameAsync(model.Email);
// 通過驗證才能用此功能
if( user != null && await _userManager.IsEmailConfirmedAsync(user))
{
string token = await _userManager.GeneratePasswordResetTokenAsync(user);
var passwordResetLink = Url.Action("ResetPassword", "Account", new { email = model.Email, token }, Request.Scheme);
_logger.LogInformation($"發送驗證信連結:{passwordResetLink}");
return View("ForgetPasswordConfirmation");
}
// 為了防暴力攻擊,不回應任何訊息
return View("ForgetPasswordConfirmation");
}
return View(model);
}
|
密碼重置功能
ViewModel 原本 DataAnntation 比較密碼第一個參數改成nameof(Password)
,可以讓程式可維護性。最近看到 EF 5 設定 Index也是這樣用。
1
|
[Compare("Password",ErrorMessage = "密碼不一置,請重新確認")]
|
改成
1
|
[Compare(nameof(Password),ErrorMessage = "密碼不一置,請重新確認")]
|
相關設定也請看GIT。
GIT: 重置密碼功能 · malagege/NetCoreAuthSample@7507387
修改令牌時效限制
令牌時效為1天
。修改時間方法參照下面程式,這一段我就不放到GIT。
1
2
3
4
|
services.Configure<DataProtectionTokenProviderOptions>(options =>
{
options.TokenLifespan = TimeSpan.FromSeconds(10);
});
|
特定權杖時效限制
參照這篇設定變更電子郵件權杖生命週期,這邊簡單紀錄,但不記到 GIT。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class CustomEmailConfirmationTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public EmailConfirmationTokenProviderOptions()
{
Name = "EmailDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(4);
}
}
|
1
2
3
4
5
6
7
8
9
10
|
builder.Services.AddDefaultIdentity<IdentityUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(
typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
}).AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();
|
簡單跟 Claim 和 Handle 處理有點類似。
其他 userManager 小功能紀錄
- 現有帳號更改密碼
_userManager.ChangePasswordAsync(user, 舊密碼,新密碼)
。
- 第三方沒設置密碼,新增密碼。
_userManager.AddPasswordAsync(user, 新密碼)
,理論上可能用不到這個功能。後續登入中使用者要用_userManager.RefreshSignInAsync(user)
更新使用者登入。
設定密碼太多次錯誤停用帳號
services.Configure<IdentityOptions>
加入設定
_signInManager.PasswordSignInAsync
第四個參數記得要修改成true
參考如下:
1
2
3
4
5
6
7
8
|
services.Configure<IdentityOptions>(options =>
{
...
// 預設帳號密碼輸入五次會封鎖15分鐘(封鎖時間預設是5分鐘)
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
});
|
_signInManager.PasswordSignInAsync
第四個參數記得要修改成true
1
|
Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, true);
|
我們嘗試輸入多組錯誤密碼明顯看到資料庫有修改。
登入中調整登入成功顯示帳號被鎖。重置密碼功能也要設定解除鎖定。
LoginAsync
方法
1
2
3
4
5
|
// 使用者封鎖不讓使用者登入,但我建議可以不提示讓使用者知道,可發信通知使用者知道
//if (result.IsLockedOut)
//{
// return View("AccountLocked");
//}
|
ResetPasswordAsync
方法
1
2
3
4
5
6
7
8
9
10
|
if(result.Succeeded)
{
// 讓當前已封鎖使用者解除封鎖動作
if ( await _userManager.IsLockedOutAsync(user))
{
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow);
}
return View("ResetPasswordConfirmation");
}
|
GIT: 設定密碼太多次錯誤停用帳號 · malagege/NetCoreAuthSample@e52ee78
心得
其實還有很多沒有記,但目前先整理到這邊。
像是
- Jwtbearer
- AddIdentityServer
- Two-Factor
很多寶藏還沒挖,有時間再整理研究。
彩蛋
How to confirm a phone number in ASP.Net Core 1.1MVC - Stack Overflow
ASP.NET Core 自动刷新JWT Token_My IO的技术博客_51CTO博客
ASP.NET Core Web Api之JWT刷新Token(三) - Jeffcky - 博客园