UserManager 中调用 Token 生成时一共用到了三种盐:
public class UserManager<TUser> : IDisposable where TUser : class{/// <summary>/// The data protection purpose used for the reset password related methods./// </summary>public const string ResetPasswordTokenPurpose = "ResetPassword";/// <summary>/// The data protection purpose used for the change phone number methods./// </summary>public const string ChangePhoneNumberTokenPurpose = "ChangePhoneNumber";/// <summary>/// The data protection purpose used for the email confirmation related methods./// </summary>public const string ConfirmEmailTokenPurpose = "EmailConfirmation";...}
真正的 Token 生成代码在 DataProtectionTokenProvider 中。
依据当前时间、userId、默认用途和 SecurityStamp 进行 Token 生成,最后转成 Base64 字符串:
/// <summary>/// Generates a protected token for the specified <paramref name="user"/> as an asynchronous operation./// </summary>/// <param name="purpose">The purpose the token will be used for.</param>/// <param name="manager">The <see cref="UserManager{TUser}"/> to retrieve user properties from.</param>/// <param name="user">The <typeparamref name="TUser"/> the token will be generated from.</param>/// <returns>A <see cref="Task{TResult}"/> representing the generated token.</returns>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);}
SecurityStamp:
验证 Token:
- 从 Base64 转回来
- 判定是否已过期
- 对比 userId
- 验证 Token 的用途(purpose)
对比安全时间戳(SecurityStamp)
/// <summary>/// Validates the protected <paramref name="token"/> for the specified <paramref name="user"/> and <paramref name="purpose"/> as an asynchronous operation./// </summary>/// <param name="purpose">The purpose the token was be used for.</param>/// <param name="token">The token to validate.</param>/// <param name="manager">The <see cref="UserManager{TUser}"/> to retrieve user properties from.</param>/// <param name="user">The <typeparamref name="TUser"/> the token was generated for.</param>/// <returns>/// A <see cref="Task{TResult}"/> that represents the result of the asynchronous validation,/// containing true if the token is valid, otherwise false./// </returns>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 EmptyGeneralCatchClausecatch{// Do not leak exception}return false;}
