如何设置由GenerateUserTokenAsync在asp.net核心中创建的令牌的TimeSpan



我有"忘记密码";页面。在这个页面上,我使用_userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");创建了6位数字然后将此代码发送给用户。现在我想设置这个代码的时间跨度,让它在2分钟后过期。我向服务容器添加了一个自定义提供程序,但它不适用于GenerateUserTokenAsync创建的代码。而且我不想设置适用于所有令牌的timeSpan。如何为此设置自定义时间跨度?

这是我的ForgotPassword方法:

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

var code = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");//create 6 digit code and use it in send sms

code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
sendSms(code);                
return RedirectToPage("./ForgotPasswordConfirmation");
}
return Page();
}

这是ResetPassword方法:

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");
}
var tokenVerified = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose", Input.Code);//check validate 6 digit code
if (!tokenVerified)
return Page();
var token = await _userManager.GeneratePasswordResetTokenAsync(user);//new token for reseting password
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的自定义提供程序。因此,该配置仅适用于方法对GeneratePasswordResetTokenAsyncResetPasswordAsync。但是,您只在内部使用这些方法来使用非常短暂的令牌实际执行密码重置。因此,这里实际上不需要寿命限制,因为令牌永远不会离开应用程序,并且无论如何都会在同一秒内立即使用。

相反,您正在做的是使用DefaultPhoneProvider创建通过SMS发送的密码令牌:

var code = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");

现在,默认的电话提供商使用基于TOTP的实现,即基于时间的一次性密码。这些密码在设计时受到限制,并且在无限长的时间内无效。根据规范,单个令牌被视为有效的时间窗口相当小。

电话提供商使用的实施方式使用3分钟的固定时间窗口,并且在每个方向上还接受两个额外的时间窗口来考虑时间偏移,从而产生用户有机会检索SMS并输入代码的总共15分钟的理论范围。

如果你想进一步限制这一点,你应该首先考虑到没有保证的短信发送时间,因此如果短信没有按时发送,或者用户无法足够快地输入,选择一个太小的窗口可能会锁定用户。

不过,如果您确实想走这条路,您可以为使用不同窗口大小的TOTP令牌提供程序提供自己的实现。请注意,由于实现是内部的,因此您必须自己实现令牌生成。

最新更新