AspnetIdentity 在哪里存储 UserTokens?

Where does AspnetIdentity stores UserTokens?

我有 ASP.Net MVC5 应用程序。在创建应用程序时 select 个人身份验证 visual studio 使用脚手架并添加用于登录、创建用户、忘记密码等的代码。我正在使用 ForgotPassword 功能的默认实现。这就是忘记密码的工作原理。

1> 用户点击忘记密码 link。

2> 用户输入他的电子邮件地址。

3> 应用程序创建新令牌并发送带有 return URL 且查询字符串中包含令牌的电子邮件。

4> 用户点击电子邮件中的 link 并被重定向到 ResetPassword 页面。

5> 用户输入新密码并点击提交。

6> 应用程序验证用户和令牌并重置密码。

问题。

1> 收到电子邮件后,用户可能不会立即点击 link,他可能会在一段时间甚至几天后点击。 ASP.NET 应用程序在哪里存储令牌?我没有在数据库中看到它。

2> 令牌是否过期。

3> 在忘记密码屏幕上,用户输入他的电子邮件并单击提交以接收带有令牌的电子邮件。假设他这样做了 3 次。所以他收到了 3 封电子邮件,其中包含 3 个不同的令牌。然后他从任何一封电子邮件中单击 link 并重置密码。其他 2 封电子邮件中未使用的令牌会怎样,它们是否仍然有效?用户可以在其他电子邮件中单击 link 并重设密码吗?

基本上,如果您查看由 ASP.Net Identity 创建的用户,您会看到一列 SecurityStamp,它基本上是哈希值,用于所有与密码相关的场景。事实上,当用户更改其密码时,它也会更改。

当您点击重置密码时,与该用户关联的 SecurityStamp 用于生成通过电子邮件发送的令牌。在此处查看 GenerateAsync 方法中的源代码(有 2 个提供程序)

https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/TotpSecurityStampBasedTokenProvider.cs

public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
    {
        if (manager == null)
        {
            throw new ArgumentNullException(nameof(manager));
        }
        var token = await manager.CreateSecurityTokenAsync(user);
        var modifier = await GetUserModifierAsync(purpose, manager, user);
        return Rfc6238AuthenticationService.GenerateCode(token, modifier).ToString("D6", CultureInfo.InvariantCulture);
}

https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/DataProtectionTokenProvider.cs

    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);
    }

一旦用户点击 link 并提交在电子邮件中发送的新密码令牌也会被提交并根据 SecurityStamp 进行验证。

    public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user)
    {
        if (manager == null)
        {
            throw new ArgumentNullException(nameof(manager));
        }
        int code;
        if (!int.TryParse(token, out code))
        {
            return false;
        }
        var securityToken = await manager.CreateSecurityTokenAsync(user);
        var modifier = await GetUserModifierAsync(purpose, manager, user);
        return securityToken != null && Rfc6238AuthenticationService.ValidateCode(securityToken, code, modifier);
}

    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;
}

一旦成功验证了令牌身份系统,就会使用新的 SecurityStamp 进一步更新用户详细信息。以上就是对您问题的回答

Ans 1 - 令牌未存储,电子邮件中的 link 立即激活。我有一个经过全面测试的生产系统,我和用户都不必等待 link 激活。

答案 2- 我认为默认值为 1 天。您可以通过在 ApplicationUserManager Class

Create 方法中添加以下代码来更改它
if (dataProtectionProvider != null)
 {
  manager.UserTokenProvider =
   new DataProtectorTokenProvider<ApplicationUser>
      (dataProtectionProvider.Create("ASP.NET Identity"))
      {                    
         TokenLifespan = TimeSpan.FromHours(1) //Any custom TimeSpan
      };
 }

答案 3 - 只有 1 个会起作用,即无论哪个用户先点击,因为在安全标记更改之后,它将使其他 2 封电子邮件中的令牌失效。

希望这对您有所帮助。