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

Posted

技术标签:

【中文标题】如何从 ASP.NET Core 中的密码重置令牌中检索用户?【英文标题】:How can I retrieve a user from a password reset token in ASP.NET Core? 【发布时间】:2020-05-04 17:22:46 【问题描述】:

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

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

以下是在密码重置 URL (Microsoft Password Recovery Doc) 中嵌入用户 ID 的一些示例代码:

var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new  userId = user.Id, code = code , protocol: HttpContext.Request.Scheme);

【问题讨论】:

您已经将用户 ID 添加到请求中......只需使用该用户 ID 来获取用户。 【参考方案1】:

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

什么是默认值

如果你有如下代码,

services.AddIdentity<AppUser, AppRole>(options =>

    ...

.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();

最后一行.AddDefaultTokenProviders()adds 4 default token providers,用于生成用于重置密码、更改电子邮件和更改电话号码选项的令牌,以及用于生成双因素身份验证令牌,进入管道:

    DataProtectorTokenProvider PhoneNumberTokenProvider EmailTokenProvider AuthenticatorTokenProvider

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

DataProtectorTokenProvider内,它的保护者是default to the name of "DataProtectorTokenProvider"。

令牌是如何生成的

如果您查看DataProtectorTokenProvider 中的GenerateAsync() 方法,您可以知道令牌包括:

令牌创建的 Utc 时间戳 (DateTimeOffset.UtcNow) userId 目的字符串 安全标记(如果支持)

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

如何获取用户ID

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

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

这里的棘手部分是如何获得用于生成这些令牌的相同DataProtector

如何获取默认 Data Protector

由于默认的DataProtectorTokenProvider 被添加到管道中,我能想到的获得相同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();

                ...
            
        

        ...
    

截图:

我的 2 美分

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

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

如果您必须使用整数作为 userId,我只需在用户个人资料(我称之为 PublicToken)中创建一个 unique_identifier 类型的列,并使用它来识别所有公共事务的用户。

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

【讨论】:

【参考方案2】:

我相信你无法做到,你可以通过用户电子邮件然后找到它在你的代码中寻找用户

public async Task<IActionResult> ResetPassword([FromBody]ResetPasswordViewModel model)

    if (string.IsNullOrEmpty(model.Token) || string.IsNullOrEmpty(model.Email))
    
        return RedirectToAction("Index", "Error", new  statusCode = AppStatusCode.NotFound );
    

    var isResetTokenValid = await _userManager.CheckValidResetPasswordToken(model.Token, model.Email);

    if (!isResetTokenValid || string.IsNullOrEmpty(model.Email))
    
        return StatusCode(AppStatusCode.ResetPassTokenExpire);
    

    var user = await _userManager.FindByEmailAsync(model.Email);
    if (user == null)
    
        return Ok();
    

    await _userManager.ResetPasswordAsync(user, model.Token, model.Password);
    return Ok();

您可以查看实现细节here

【讨论】:

【参考方案3】:

在这种情况下,我所做的是将新令牌保存在缓存或带有用户 ID 的 sql 表中。这样,您首先查询包含重置令牌的表,如果需要则对其进行验证并获取用户。

【讨论】:

以上是关于如何从 ASP.NET Core 中的密码重置令牌中检索用户?的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 2.2 - 密码重置在 Azure 上不起作用(无效令牌)

ASP.NET Core Identity - 使令牌(电子邮件确认、密码重置等)在不同服务器上有效?

ASP.NET 核心中的密码重置令牌提供程序 - 找不到 IUserTokenProvider

如何在 asp.net core mvc 中配置确认电子邮件令牌的生命周期

从 ASP.NET Core 中的 API 读取 JWT 令牌

使用 ASP.NET 身份重置密码时令牌无效