如何从 ASP.NET Core 中的密码重置令牌中检索用户?

Sco*_*ott 3 c# forgot-password asp.net-core asp.net-core-identity

我有一个 ASP.NET Core 2.1 Web 应用程序,并且正在添加忘记密码功能。我看过几个例子,他们似乎采取了两种方法之一。第一种方法是在密码重置 url 中包含用户 ID 或用户的电子邮件以及密码重置令牌。第二种方法是在密码重置 url 中仅包含密码重置令牌,然后要求用户在尝试更改密码时输入识别信息(例如电子邮件)(二进制智能示例)。有没有办法在仅给定密码重置令牌的情况下查找用户?

我的团队负责人要求我只传递密码重置 url 中的令牌,然后查找用户。我最初的研究让我相信我必须手动记录用户 ID 和令牌关系,但我希望有内置的东西。我已经查看了ASP.NET Core UserManager 文档,但没有找到任何检索方法给定令牌的用户。

下面是一些将用户 ID 嵌入密码重置 URL(Microsoft 密码恢复文档)的示例代码:

var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
Run Code Online (Sandbox Code Playgroud)

Dav*_*ang 7

有一种方法可以UserId从密码重置令牌中获取 ,但在我看来,这很棘手且需要大量工作。

什么是默认值

如果你有一些像下面这样的代码,

services.AddIdentity<AppUser, AppRole>(options =>
{
    ...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();

Run Code Online (Sandbox Code Playgroud)

最后一行.AddDefaultTokenProviders() 添加了 4 个默认令牌提供程序,用于生成用于重置密码、更改电子邮件和更改电话号码选项的令牌,以及用于生成两因素身份验证令牌的令牌到管道中:

  1. DataProtectorTokenProvider
  2. 电话号码令牌提供者
  3. 电子邮件令牌提供程序
  4. AuthenticatorTokenProvider

第一个,DataProtectorTokenProvider,是我们正在寻找的。它使用数据保护来序列化/加密这些令牌。

在 中DataProtectorTokenProvider,它的保护者默认为“DataProtectorTokenProvider”的名称

代币是如何产生的

如果您查看GenerateAsync()里面的方法DataProtectorTokenProvider,您可以看出令牌包括:

  • 令牌创建的UTC时间戳 ( DateTimeOffset.UtcNow)
  • userId
  • 目的字符串
  • 安全印章(如果支持)

generate 方法连接所有这些,将它们转换为字节数组,并调用内部的保护器来保护/加密有效负载。最后,payload 被转换为 base 64 字符串。

如何获取用户 ID

要从userId令牌中获取,您需要进行逆向工程:

  • 将标记从 base 64 字符串转换回字节数组
  • 调用内部的保护器来取消保护/解密字节数组
  • 读取UTC时间戳
  • userId

这里棘手的部分是如何DataProtector使用相同的方法来生成这些令牌!

如何获取默认的 Data Protector

由于默认DataProtectorTokenProvider是 DIed 到管道中,所以我能想到的唯一方法DataProtector是使用默认DataProtectorTokenProvider创建一个具有相同默认名称“DataProtectorTokenProvider”的保护器,用于生成令牌!

public class GetResetPasswordViewModelHandler : IRequestHandler<...>
{
    ...
    private readonly IDataProtector _dataProtector;

    public GetResetPasswordViewModelHandler(...,
       IDataProtectionProvider dataProtectionProvider)
    {
        ...
        _dataProtector = dataProtectionProvider.CreateProtector("DataProtectorTokenProvider");
        // OR
        // dataProtectionProvider.CreateProtector(new DataProtectionTokenProviderOptions().Name);
    }

    public async Task<ResetPasswordViewModel> Handle(GetResetPasswordViewModel query, ...)
    {
        // The password reset token comes from query.ResetToken
        var resetTokenArray = Convert.FromBase64String(query.ResetToken);

        var unprotectedResetTokenArray = _dataProtector.Unprotect(resetTokenArray);

        using (var ms = new MemoryStream(unprotectedResetTokenArray))
        {
            using (var reader = new BinaryReader(ms))
            { 
                // Read off the creation UTC timestamp
                reader.ReadInt64();

                // Then you can read the userId!
                var userId = reader.ReadString();

                ...
            }
        }

        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

截屏: 在此处输入图片说明

我的 2 美分

似乎只是尝试读取userId密码重置令牌需要做很多工作。我知道您的团队负责人可能不想在密码重置链接上公开用户 ID,或者他认为这是多余的,因为重置令牌具有userId.

如果您使用整数来表示userId并且不想将其公开给公众,我会将其更改为GUID.

如果您必须使用 integer 作为您的userId,我只需在用户配置文件中创建一个类型为 unique_identifier 的列(我将其称为 PublicToken)并使用它来标识所有公共事务的用户。

var callbackUrl = Url.Action("resetPassword", "account", new
{
    area = "",
    rt = passwordResetToken,   // reset token
    ut = appUser.Id            // user token, use GUID user id or appUser.PublicToken
}, protocol: Request.Scheme);

Run Code Online (Sandbox Code Playgroud)