我的项目中有“忘记密码”页面。在此页面,我使用
_userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");
创建 6 位数字
然后将此代码发送给用户。现在我想设置此代码的时间跨度,使其在 2 分钟后过期。我向服务容器添加了一个自定义提供程序,但它不适用于由 GenerateUserTokenAsync
创建的代码。而且我也不想设置适用于所有令牌的 timeSpan。我如何为此设置自定义时间跨度?
这是我的忘记密码方法:
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
return RedirectToPage("./ForgotPasswordConfirmation");
}
//create 6 digit code and use it in send sms
var code = await _userManager
.GenerateUserTokenAsync(user,
TokenOptions.DefaultPhoneProvider,
"ResetPasswordTokenPurpose");
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
sendSms(code);
return RedirectToPage("./ForgotPasswordConfirmation");
}
return Page();
}
这是重置密码方法:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
//check validate 6 digit code
var tokenVerified = await _userManager
.VerifyUserTokenAsync(user,
TokenOptions.DefaultPhoneProvider,
"ResetPasswordTokenPurpose",
Input.Code);
if (!tokenVerified)
return Page();
//new token for reseting password
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var result = await _userManager.ResetPasswordAsync(user, token, Input.Password);
if (result.Succeeded)
{
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
更改 ResetPassword 令牌的生命周期:
public class CustomResetPasswordTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomResetPasswordTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<ResetPasswordTokenProviderOptions> options)
: base(dataProtectionProvider, options)
{
}
}
public class ResetPasswordTokenProviderOptions
: DataProtectionTokenProviderOptions
{
public ResetPasswordTokenProviderOptions()
{
Name = "ResetPasswordProtectorTokenProvider";
TokenLifespan = TimeSpan.FromMinutes(2);
}
}
在StartUp.cs中调用它:
services
.AddDefaultIdentity<IdentityUser>(config =>
{
config.Tokens.ProviderMap.Add("CustomResetPassword",
new TokenProviderDescriptor(
typeof(CustomResetPasswordTokenProvider<IdentityUser>)));
config.Tokens.PasswordResetTokenProvider = "CustomResetPassword";
})
.AddEntityFrameworkStores<ApplicationDbContext>();
services
.AddTransient<CustomResetPasswordTokenProvider<IdentityUser>>();
config.Tokens.PasswordResetTokenProvider = "CustomResetPassword";
在您的配置中,您只需为
PasswordResetTokenProvider
设置自定义提供程序。因此,此配置仅适用于方法对 GeneratePasswordResetTokenAsync
和 ResetPasswordAsync
。但是您仅在内部使用这些方法来使用非常短暂的令牌实际执行密码重置。因此,这里的生命周期限制实际上是不需要的,因为令牌永远不会离开应用程序,并且无论如何都会在同一秒内立即使用。
相反,您正在做的是使用
DefaultPhoneProvider
创建通过短信发送的密码令牌:
var code = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");
现在,默认电话提供商使用基于 TOTP 的实现,基于时间的一次性密码。这些密码在设计上是有时间限制的,并且不能无限期有效。 根据规范,单个令牌被视为有效的时间窗口应该相当小。
电话提供商使用的实现使用 3 分钟的固定时间窗口,并且还接受每个方向上的两个附加时间窗口以考虑时移,从而导致用户拥有 15 分钟的总理论范围。有机会检索短信并输入代码。
如果您想进一步限制这一点,您应该首先考虑到没有保证短信发送时间,因此选择太小的窗口可能会在短信未按时发送或无法输入时锁定用户足够快。
如果您确实想走这条路,您可以为 TOTP 令牌提供程序 提供您自己的实现,它使用不同的窗口大小。请注意,您必须自己实现令牌生成,因为实现是内部。