UserManager 中调用 Token 生成时一共用到了三种盐:

    1. public class UserManager<TUser> : IDisposable where TUser : class
    2. {
    3. /// <summary>
    4. /// The data protection purpose used for the reset password related methods.
    5. /// </summary>
    6. public const string ResetPasswordTokenPurpose = "ResetPassword";
    7. /// <summary>
    8. /// The data protection purpose used for the change phone number methods.
    9. /// </summary>
    10. public const string ChangePhoneNumberTokenPurpose = "ChangePhoneNumber";
    11. /// <summary>
    12. /// The data protection purpose used for the email confirmation related methods.
    13. /// </summary>
    14. public const string ConfirmEmailTokenPurpose = "EmailConfirmation";
    15. ...
    16. }

    真正的 Token 生成代码在 DataProtectionTokenProvider 中。

    依据当前时间、userId、默认用途和 SecurityStamp 进行 Token 生成,最后转成 Base64 字符串:

    1. /// <summary>
    2. /// Generates a protected token for the specified <paramref name="user"/> as an asynchronous operation.
    3. /// </summary>
    4. /// <param name="purpose">The purpose the token will be used for.</param>
    5. /// <param name="manager">The <see cref="UserManager{TUser}"/> to retrieve user properties from.</param>
    6. /// <param name="user">The <typeparamref name="TUser"/> the token will be generated from.</param>
    7. /// <returns>A <see cref="Task{TResult}"/> representing the generated token.</returns>
    8. public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
    9. {
    10. if (user == null)
    11. {
    12. throw new ArgumentNullException(nameof(user));
    13. }
    14. var ms = new MemoryStream();
    15. var userId = await manager.GetUserIdAsync(user);
    16. using (var writer = ms.CreateWriter())
    17. {
    18. writer.Write(DateTimeOffset.UtcNow);
    19. writer.Write(userId);
    20. writer.Write(purpose ?? "");
    21. string stamp = null;
    22. if (manager.SupportsUserSecurityStamp)
    23. {
    24. stamp = await manager.GetSecurityStampAsync(user);
    25. }
    26. writer.Write(stamp ?? "");
    27. }
    28. var protectedBytes = Protector.Protect(ms.ToArray());
    29. return Convert.ToBase64String(protectedBytes);
    30. }

    SecurityStamp:
    image.png

    验证 Token:

    1. 从 Base64 转回来
    2. 判定是否已过期
    3. 对比 userId
    4. 验证 Token 的用途(purpose)
    5. 对比安全时间戳(SecurityStamp)

      1. /// <summary>
      2. /// Validates the protected <paramref name="token"/> for the specified <paramref name="user"/> and <paramref name="purpose"/> as an asynchronous operation.
      3. /// </summary>
      4. /// <param name="purpose">The purpose the token was be used for.</param>
      5. /// <param name="token">The token to validate.</param>
      6. /// <param name="manager">The <see cref="UserManager{TUser}"/> to retrieve user properties from.</param>
      7. /// <param name="user">The <typeparamref name="TUser"/> the token was generated for.</param>
      8. /// <returns>
      9. /// A <see cref="Task{TResult}"/> that represents the result of the asynchronous validation,
      10. /// containing true if the token is valid, otherwise false.
      11. /// </returns>
      12. public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user)
      13. {
      14. try
      15. {
      16. var unprotectedData = Protector.Unprotect(Convert.FromBase64String(token));
      17. var ms = new MemoryStream(unprotectedData);
      18. using (var reader = ms.CreateReader())
      19. {
      20. var creationTime = reader.ReadDateTimeOffset();
      21. var expirationTime = creationTime + Options.TokenLifespan;
      22. if (expirationTime < DateTimeOffset.UtcNow)
      23. {
      24. return false;
      25. }
      26. var userId = reader.ReadString();
      27. var actualUserId = await manager.GetUserIdAsync(user);
      28. if (userId != actualUserId)
      29. {
      30. return false;
      31. }
      32. var purp = reader.ReadString();
      33. if (!string.Equals(purp, purpose))
      34. {
      35. return false;
      36. }
      37. var stamp = reader.ReadString();
      38. if (reader.PeekChar() != -1)
      39. {
      40. return false;
      41. }
      42. if (manager.SupportsUserSecurityStamp)
      43. {
      44. return stamp == await manager.GetSecurityStampAsync(user);
      45. }
      46. return stamp == "";
      47. }
      48. }
      49. // ReSharper disable once EmptyGeneralCatchClause
      50. catch
      51. {
      52. // Do not leak exception
      53. }
      54. return false;
      55. }