轉載請註入出處: https://home.cnblogs.com/u/zhiyong-ITNote/ dotnet core中提供了一個新的身份驗證框架Identity,它不同於dot net下的身份驗證。在這個框架裡面,有一個生成token的功能,也就是我們常說的令牌,令牌的作用有哪些?Toke ...
轉載請註入出處: https://home.cnblogs.com/u/zhiyong-ITNote/
dotnet core中提供了一個新的身份驗證框架Identity,它不同於dot net下的身份驗證。在這個框架裡面,有一個生成token的功能,也就是我們常說的令牌,令牌的作用有哪些?
Token值介紹
token 值: 登錄令牌.利用 token 值來判斷用戶的登錄狀態.類似於 MD5 加密之後的長字元串.
用戶登錄成功之後,在後端(伺服器端)會根據用戶信息生成一個唯一的值.這個值就是 token 值.
基本使用:
在伺服器端(資料庫)會保存這個 token 值,以後利用這個 token 值來檢索對應的用戶信息,並且判斷用戶的登錄狀態.
用戶登錄成功之後,伺服器會將生成的 token 值返回給 客戶端,在客戶端也會保存這個 token 值.(一般可以保存在 cookie 中,也可以自己手動確定保存位置(比如偏好設置.)).
以後客戶端在發送新的網路請求的時候,會預設自動附帶這個 token 值(作為一個參數傳遞給伺服器.).伺服器拿到客戶端傳遞的 token 值跟保存在 資料庫中的 token 值做對比,以此來判斷用戶身份和登錄狀態.
判斷登錄狀態:
如果客戶端沒有這個 token 值,意味著沒有登錄成功過,提示用戶登錄.
如果客戶端有 token 值,一般會認為登錄成功.不需要用戶再次登錄(輸入賬號和密碼信息).
token 值擴展:
token 值有失效時間:
一般的 app ,token值得失效時間都在 1 年以上.
特殊的 app :銀行類 app /支付類 app :token值失效時間 15 分鐘左右.
一旦用戶信息改變(密碼改變),會在伺服器生成新的 token 值,原來的 token值就會失效.需要再次輸入賬號和密碼,以得到生成的新的 token 值.
唯一性判斷: 每次登錄,都會生成一個新的token值.原來的 token 值就會失效.利用時間來判斷登錄的差異性.
至此也就說完了其作用。那麼Identity框架是如何為我們提供這個能力的呢?我們又該如何使用呢?首先請你看下 asp.net core中的數據保護模塊,這是Identity框架實現token的基礎。
var token = await userManager.GenerateUserTokenAsync(user, "Default", "passwordless-auth");
我們通過這句代碼來生成token,userManager是Identity框架的用戶管理類UserManager的實例對象。
當然首先我們需要實現相關的依賴註入,我就不說了。Identity預設生成的token是基於DataProtectorTokenProvider類的,我們在依賴註入的時候,其實就引用了這個類,先看下AddDefaultTokenProviders的源碼:
public static IdentityBuilder AddDefaultTokenProviders(this IdentityBuilder builder) { var userType = builder.UserType; var dataProtectionProviderType = typeof(DataProtectorTokenProvider<>).MakeGenericType(userType); var phoneNumberProviderType = typeof(PhoneNumberTokenProvider<>).MakeGenericType(userType); var emailTokenProviderType = typeof(EmailTokenProvider<>).MakeGenericType(userType); var authenticatorProviderType = typeof(AuthenticatorTokenProvider<>).MakeGenericType(userType); return builder.AddTokenProvider(TokenOptions.DefaultProvider, dataProtectionProviderType) .AddTokenProvider(TokenOptions.DefaultEmailProvider, emailTokenProviderType) .AddTokenProvider(TokenOptions.DefaultPhoneProvider, phoneNumberProviderType) .AddTokenProvider(TokenOptions.DefaultAuthenticatorProvider, authenticatorProviderType); }
然後我們看下DataProtectorTokenProvider類的部分源碼:
public class DataProtectorTokenProvider<TUser> : IUserTwoFactorTokenProvider<TUser> where TUser : class { /// <summary> /// Initializes a new instance of the <see cref="DataProtectorTokenProvider{TUser}"/> class. /// </summary> /// <param name="dataProtectionProvider">The system data protection provider.</param> /// <param name="options">The configured <see cref="DataProtectionTokenProviderOptions"/>.</param> public DataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions<DataProtectionTokenProviderOptions> options) { if (dataProtectionProvider == null) { throw new ArgumentNullException(nameof(dataProtectionProvider)); } Options = options?.Value ?? new DataProtectionTokenProviderOptions(); // Use the Name as the purpose which should usually be distinct from others Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider"); } public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } var ms = new MemoryStream(); var userId = await manager.GetUserIdAsync(user); using (var writer = ms.CreateWriter()) { writer.Write(DateTimeOffset.UtcNow); writer.Write(userId); writer.Write(purpose ?? ""); string stamp = null; if (manager.SupportsUserSecurityStamp) { stamp = await manager.GetSecurityStampAsync(user); } writer.Write(stamp ?? ""); } var protectedBytes = Protector.Protect(ms.ToArray()); return Convert.ToBase64String(protectedBytes); } public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user) { try { var unprotectedData = Protector.Unprotect(Convert.FromBase64String(token)); var ms = new MemoryStream(unprotectedData); using (var reader = ms.CreateReader()) { var creationTime = reader.ReadDateTimeOffset(); var expirationTime = creationTime + Options.TokenLifespan; if (expirationTime < DateTimeOffset.UtcNow) { return false; } var userId = reader.ReadString(); var actualUserId = await manager.GetUserIdAsync(user); if (userId != actualUserId) { return false; } var purp = reader.ReadString(); if (!string.Equals(purp, purpose)) { return false; } var stamp = reader.ReadString(); if (reader.PeekChar() != -1) { return false; } if (manager.SupportsUserSecurityStamp) { return stamp == await manager.GetSecurityStampAsync(user); } return stamp == ""; } } // ReSharper disable once EmptyGeneralCatchClause catch { // Do not leak exception } return false; } }View Code
可以看到,預設的情況下,使用的就是asp.net core Data Provider生成token的。我們來看看這個DataProtectorTokenProviderOptions配置類的信息:
public class DataProtectionTokenProviderOptions { public string Name { get; set; } = "DataProtectorTokenProvider"; public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1); }
預設的情況下,token過期時間是一天,也就是說,如果我們需要修改過期時間的話,完全可以自己來:
services.Configure<DataProtectionTokenProviderOptions>( x => x.TokenLifespan = TimeSpan.FromMinutes(15));
剛剛也說了,預設情況下Identity框架給我們提供的token是基於Data Protect的,那麼我們可以切換另外的token提供方式。只需繼承自TotpSecurityStampBasedTokenProvider<TUser>類就可以了。該類源碼地址:
https://github.com/aspnet/Identity/blob/c7276ce2f76312ddd7fccad6e399da96b9f6fae1/src/Core/TotpSecurityStampBasedTokenProvider.cs#L21 該類與DataProtectorTokenProvider類一樣都是繼承自IUserTwoFactorTokenProvider介面來實現的,其方法都是一樣的,只是該類的token生成演算法不再是Data Protect中的token生成演算法了,而是hash演算法,因此這裡也是一個可拓展點,我們還可以提供自己的想要的加密演算法來生成token。
public class PasswordlessLoginTotpTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser> where TUser : class { public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user) { return Task.FromResult(false); } public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager, TUser user) { var email = await manager.GetEmailAsync(user); return "PasswordlessLogin:" + purpose + ":" + email; } } public static class CustomIdentityBuilderExtensions { public static IdentityBuilder AddPasswordlessLoginTotpTokenProvider(this IdentityBuilder builder) { var userType = builder.UserType; var totpProvider = typeof(PasswordlessLoginTotpTokenProvider<>).MakeGenericType(userType); return builder.AddTokenProvider("PasswordlessLoginTotpProvider", totpProvider); } } public void ConfigureServices(IServiceCollection services) { services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<IdentityDbContext>() .AddDefaultTokenProviders() .AddPasswordlessLoginTotpTokenProvider(); // Add the custom token provider } var token = await userManager.GenerateUserTokenAsync( user, "PasswordlessLoginTotpProvider", "passwordless-auth");View Code
這就是我們的使用了。在這裡提一下。TotpSecurityStampBasedTokenProvider類的預設生命周期是9分鐘,一旦超過9分鐘,token就會變化。
大概的理了下自己接下來的實現需要用到的技術,不能為了實現而實現,而是要想清楚需要的技術,同時去學習這些技術,然後再去實現。其實這個需求是我自己加的,因為之前看官方文檔的時候,對數據保護很懵逼,只知道可以用來生成token加密,卻不知道實際的用途,因此自己深入研究查找資料,借鑒別人的博客來學習,還是google好啊,百度真...就不爆粗口了。
整理下自己需要實現得需求思路:
我們可以在郵箱驗證的時候用到token,也可以在整個的網站中用到,第一個就先不說了,說說第二個:
我們一般可以將用戶登陸之後,根據用戶名什麼的生成token,然後放到服務端也就是資料庫了,我們給它有效期(比如連續記住我十天之類的)表示這個用戶已經登錄過了。那麼客戶端呢?也就是我們的瀏覽器,我們可以將cookie存入客戶端瀏覽器,但是一旦我們關閉了瀏覽器那麼cookie也就不存在了,因此不可行。可以存入到本地磁碟中。同時,一般來講,網站的url都很長,一般都是將token拼接在了url裡面,如果你覺得url太長了,我們可以在中間件中寫入到http頭部欄位中,根據http頭部欄位。當然你可以在ActionFilter以及ResultFilter中寫入http頭部。
回頭看看能不能整理出來一個實現,到時候再放上來代碼。
參考資料:
Implementing custom token providers for passwordless authentication in ASP.NET Core Identity
Implementing Medium's Passwordless Authentication using ASP.NET Core Identity
轉載請註入出處: https://home.cnblogs.com/u/zhiyong-ITNote/