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 EmptyGeneralCatchClause
catch
{
// Do not leak exception
}
return false;
}