简体   繁体   English

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

[英]How can I retrieve a user from a password reset token in ASP.NET Core?

I have an ASP.NET Core 2.1 web application and am adding forgot password functionality.我有一个 ASP.NET Core 2.1 Web 应用程序,并且正在添加忘记密码功能。 I have looked at several examples, and they seem to take one of two approaches.我看过几个例子,他们似乎采取了两种方法之一。 The first approach is to include either the user id or the user's email in the password reset url along with the password reset token.第一种方法是在密码重置 url 中包含用户 ID 或用户的电子邮件以及密码重置令牌。 The second approach is to include only the password reset token in the password reset url and then require the user to enter identifying information (such as email) when attempting to change the password ( Binary Intellect example ).第二种方法是在密码重置 url 中仅包含密码重置令牌,然后要求用户在尝试更改密码时输入识别信息(例如电子邮件)( 二进制智能示例)。 Is there a way to look up the user given just the password reset token?有没有办法在仅给定密码重置令牌的情况下查找用户?

My team lead has asked me to pass just the token in the password reset url and then look up the user.我的团队负责人要求我只传递密码重置 url 中的令牌,然后查找用户。 My initial research makes me believe that I would have to manually keep record of the user id and token relationship, but am hoping that there's something built in. I have reviewed the ASP.NET Core UserManager documentation , but did not find any methods for retrieving a user for a given token.我最初的研究让我相信我必须手动记录用户 ID 和令牌关系,但我希望有内置的东西。我已经查看了ASP.NET Core UserManager 文档,但没有找到任何检索方法给定令牌的用户。

Here's some of the example code embedding the user id in the password reset URL ( Microsoft Password Recovery Doc ):下面是一些将用户 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);

There is a way to get the UserId from the password reset token, but in my opinion it's tricky and a lot of work.有一种方法可以从密码重置令牌中获取UserId ,但在我看来,这很棘手且需要大量工作。

What are the defaults什么是默认值

If you have some codes like the following,如果你有一些像下面这样的代码,

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

the last line .AddDefaultTokenProviders() adds 4 default token providers , which are used to generate tokens for reset passwords, change email and change phone number options, and for two factor authentication token generation, into the pipeline:最后一行.AddDefaultTokenProviders() 将 4 个默认令牌提供程序添加到管道中,用于生成用于重置密码、更改电子邮件和更改电话号码选项以及用于生成两因素身份验证令牌的令牌:

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

The first one, DataProtectorTokenProvider , is what we're looking for.第一个DataProtectorTokenProvider是我们正在寻找的。 It uses data protection to serialize/encrypt those tokens.它使用数据保护来序列化/加密这些令牌。

And within the DataProtectorTokenProvider , its protector is default to the name of "DataProtectorTokenProvider" .而在DataProtectorTokenProvider ,它的保护者默认为 "DataProtectorTokenProvider" 的名称

How tokens are generated代币是如何产生的

If you look at GenerateAsync() method inside DataProtectorTokenProvider , you can kind of tell the token consists of:如果您查看DataProtectorTokenProvider中的GenerateAsync()方法,您可以看出令牌包括:

  • Utc timestamp of the token creation ( DateTimeOffset.UtcNow )令牌创建的 Utc 时间戳( DateTimeOffset.UtcNow
  • userId
  • Purpose string目的字符串
  • Security stamp, if supported安全印章(如果支持)

The generate method concatenates all those, transform them to a byte array, and calls the protector inside to protect/encrypt the payload. generate 方法连接所有这些,将它们转换为字节数组,并调用内部的保护器来保护/加密有效负载。 Finally the payload is converted to a base 64 string.最后,payload 被转换为 base 64 字符串。

How to get User Id如何获取用户 ID

To get the userId from a token, you need to do the reverse engineering:要从令牌中获取userId ,您需要进行逆向工程:

  • Convert the token from base 64 string back to the byte array将标记从 base 64 字符串转换回字节数组
  • Call the protector inside to unprotect/decrypt the byte array调用内部的保护器来取消保护/解密字节数组
  • Read off the Utc timestamp读取UTC时间戳
  • Read userId读取用户userId

The tricky part here is how to get the same DataProtector used to generate those token!这里棘手的部分是如何获取用于生成这些令牌的相同DataProtector

How to get the default Data Protector如何获取默认的 Data Protector

Since the default DataProtectorTokenProvider is DIed into the pipeline, the only way I can think of to get the same DataProtector is to use the default DataProtectorTokenProvider to create a protector with the same default name, "DataProtectorTokenProvider", used to generate tokens!由于默认的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();

                ...
            }
        }

        ...
    }
}

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

My 2 cents我的 2 美分

It seems like it's a lot of work just try to read the userId off a password reset token.似乎只是尝试从密码重置令牌中读取userId做很多工作。 I understand your team lead probably doesn't want to expose the user id on the password reset link, or (s)he thinks it's redundant since the reset token has the userId .我知道您的团队负责人可能不想在密码重置链接上公开用户 ID,或者他认为这是多余的,因为重置令牌具有userId

If you're using integer to represent the userId and don't want to expose that to public, I would change it to GUID .如果您使用整数来表示userId并且不想将其公开,我会将其更改为GUID

If you have to use integer as your userId , I would just create a column of the type unique_identifier off the user profile (I would call it PublicToken) and use that to identifier a user for all public matters.如果您必须使用 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);

I believe there is no way you can do that you can pass user email then find it look for user in your code我相信您无法通过传递用户电子邮件然后在代码中查找用户

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

You can view the implementaion detail here您可以在此处查看实现细节

What I do in this case is I keep that new token in a cache or sql table with user id in it.在这种情况下,我所做的是将新令牌保存在缓存或 sql 表中,其中包含用户 ID。 That way you first query that table containing reset token, validate it if you need it and get user.这样您首先查询包含重置令牌的表,如果需要则验证它并获取用户。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ASP.NET 核心中的密码重置令牌提供程序 - 找不到 IUserTokenProvider - Password reset token provider in ASP.NET core - IUserTokenProvider not found ASP.NET 核心。 更改密码后如何使 JWT-Token 失效 - ASP.NET Core. How can I invalidate JWT-Token after password change 如何在ASP.NET MVC中生成令牌以重置密码 - How to generate token to reset password in ASP.NET MVC 如何减少 Asp.Net Identity 中的密码重置令牌长度? - How to reduce password reset token length in Asp.Net Identity? 如何在 ASP.NET 核心标识中检索用户的 2FA 恢复代码? - How can I retrieve 2FA recovery codes for a user in ASP.NET Core Identity? 如何在 ASP.NET Core 3 中使用全文索引从数据库中检索产品 - How can I retrieve products from database with Full-Text index in ASP.NET Core 3 如何在 ASP.Net Core 5 Identity 中从 Google OAUTH 获取刷新令牌? - How can I get a refresh-token from Google OAUTH in ASP.Net Core 5 Identity? ASP.Net Core 3.1 Identity - 生成密码重置令牌问题 - ASP.Net Core 3.1 Identity - Generating Password Reset Token Issue 用密码中的*重置密码时的ASP.Net身份“无效令牌” - ASP.Net Identity “Invalid token” on password reset with * in password ASP.NET Identity WebAPI无效密码重置令牌 - ASP.NET Identity WebAPI invalid password reset token
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM