如何从 ASP.NET Core 中的密码重置令牌中检索用户?
How can I retrieve a user from a password reset token in ASP.NET Core?
我有一个 ASP.NET Core 2.1 网络应用程序,正在添加忘记密码功能。我看过几个例子,它们似乎采用了两种方法之一。第一种方法是在密码重置 url 中包含用户 ID 或用户的电子邮件以及密码重置令牌。第二种方法是在密码重置 url 中仅包含密码重置令牌,然后要求用户在尝试更改密码 (Binary Intellect example) 时输入身份信息(例如电子邮件)。有没有办法只给定密码重置令牌来查找用户?
我的团队领导要求我只传递密码重置中的令牌 url,然后查找用户。我最初的研究让我相信我必须手动记录用户 ID 和令牌关系,但我希望有一些内置的东西。我已经查看了 ASP.NET Core UserManager documentation,但没有找到任何检索 a 的方法给定令牌的用户。
下面是一些在密码重置中嵌入用户 ID 的示例代码 URL (Microsoft Password Recovery Doc):
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
我相信你无法通过用户电子邮件然后找到它在你的代码中查找用户
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
有一种方法可以从密码重置令牌中获取 UserId
,但我认为这很棘手并且需要大量工作。
默认值是什么
如果您有如下代码,
services.AddIdentity<AppUser, AppRole>(options =>
{
...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
最后一行 .AddDefaultTokenProviders()
adds 4 default token providers,用于生成用于重置密码、更改电子邮件和更改 phone 号码选项以及双因素身份验证令牌生成的令牌,进入管道:
- DataProtectorTokenProvider
- PhoneNumberTokenProvider
- EmailTokenProvider
- AuthenticatorTokenProvider
第一个,DataProtectorTokenProvider
,就是我们要找的。它对 serialize/encrypt 这些令牌使用数据保护。
而在DataProtectorTokenProvider
内,它的保护者是default to the name of "DataProtectorTokenProvider"。
令牌是如何生成的
如果您查看 DataProtectorTokenProvider
中的 GenerateAsync()
方法,您可以判断出令牌包含:
- 令牌创建的 Utc 时间戳 (
DateTimeOffset.UtcNow
)
userId
- 目的字符串
- 安全戳,如果支持的话
generate 方法连接所有这些,将它们转换为字节数组,并调用内部的保护器来 protect/encrypt 有效载荷。最后,有效负载被转换为 base 64 字符串。
如何获取用户 ID
要从令牌中获取 userId
,您需要进行逆向工程:
- 将令牌从 base 64 字符串转换回字节数组
- 调用里面的保护器到unprotect/decrypt字节数组
- 读出 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
。我了解您的团队负责人可能不想在密码重置 link 上公开用户 ID,或者他认为这是多余的,因为重置令牌具有 userId
.
如果您使用整数来表示 userId
并且不想将其公开给 public,我会将其更改为 GUID
。
如果你必须使用整数作为你的 userId
,我会在用户配置文件中创建一个类型为 unique_identifier 的列(我称之为 PublicToken)并将其用于标识符 a所有 public 事项的用户。
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);
我在这种情况下所做的是将新令牌保存在缓存中或 sql table 中包含用户 ID。这样你首先查询 table 包含重置令牌,如果你需要它验证它并获得用户。
我有一个 ASP.NET Core 2.1 网络应用程序,正在添加忘记密码功能。我看过几个例子,它们似乎采用了两种方法之一。第一种方法是在密码重置 url 中包含用户 ID 或用户的电子邮件以及密码重置令牌。第二种方法是在密码重置 url 中仅包含密码重置令牌,然后要求用户在尝试更改密码 (Binary Intellect example) 时输入身份信息(例如电子邮件)。有没有办法只给定密码重置令牌来查找用户?
我的团队领导要求我只传递密码重置中的令牌 url,然后查找用户。我最初的研究让我相信我必须手动记录用户 ID 和令牌关系,但我希望有一些内置的东西。我已经查看了 ASP.NET Core UserManager documentation,但没有找到任何检索 a 的方法给定令牌的用户。
下面是一些在密码重置中嵌入用户 ID 的示例代码 URL (Microsoft Password Recovery Doc):
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
我相信你无法通过用户电子邮件然后找到它在你的代码中查找用户
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
有一种方法可以从密码重置令牌中获取 UserId
,但我认为这很棘手并且需要大量工作。
默认值是什么
如果您有如下代码,
services.AddIdentity<AppUser, AppRole>(options =>
{
...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
最后一行 .AddDefaultTokenProviders()
adds 4 default token providers,用于生成用于重置密码、更改电子邮件和更改 phone 号码选项以及双因素身份验证令牌生成的令牌,进入管道:
- DataProtectorTokenProvider
- PhoneNumberTokenProvider
- EmailTokenProvider
- AuthenticatorTokenProvider
第一个,DataProtectorTokenProvider
,就是我们要找的。它对 serialize/encrypt 这些令牌使用数据保护。
而在DataProtectorTokenProvider
内,它的保护者是default to the name of "DataProtectorTokenProvider"。
令牌是如何生成的
如果您查看 DataProtectorTokenProvider
中的 GenerateAsync()
方法,您可以判断出令牌包含:
- 令牌创建的 Utc 时间戳 (
DateTimeOffset.UtcNow
) userId
- 目的字符串
- 安全戳,如果支持的话
generate 方法连接所有这些,将它们转换为字节数组,并调用内部的保护器来 protect/encrypt 有效载荷。最后,有效负载被转换为 base 64 字符串。
如何获取用户 ID
要从令牌中获取 userId
,您需要进行逆向工程:
- 将令牌从 base 64 字符串转换回字节数组
- 调用里面的保护器到unprotect/decrypt字节数组
- 读出 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
。我了解您的团队负责人可能不想在密码重置 link 上公开用户 ID,或者他认为这是多余的,因为重置令牌具有 userId
.
如果您使用整数来表示 userId
并且不想将其公开给 public,我会将其更改为 GUID
。
如果你必须使用整数作为你的 userId
,我会在用户配置文件中创建一个类型为 unique_identifier 的列(我称之为 PublicToken)并将其用于标识符 a所有 public 事项的用户。
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);
我在这种情况下所做的是将新令牌保存在缓存中或 sql table 中包含用户 ID。这样你首先查询 table 包含重置令牌,如果你需要它验证它并获得用户。