[英]ASP.NET Identity verify if ResetPassword token has expired
在我的API中,我有2个端点,首先生成重置密码表单的电子邮件(我使用UserManager.GeneratePasswordResetTokenAsync
生成令牌)。
第二个端点用于实际密码重置(我使用UserManager.ResetPasswordAsync
)。
我的要求是验证密码重置所需的令牌是否未过期。
在GitHub上搜索我发现了这个问题,而且从我发现的这个问题来看,设计是不可能的。
然而,深入搜索我发现UserManager.ResetPasswordAsync内部使用Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider中的 ValidateAsync
。
有了这个我创建了这个扩展方法:
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace Api.Extensions
{
public enum TokenValidity
{
VALID,
INVALID,
INVALID_EXPIRED,
ERROR
}
public static class UserManagerExtensions
{
public static TokenValidity IsResetPasswordTokenValid<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user, string token) where TKey : IEquatable<TKey> where TUser : class, IUser<TKey>
{
return IsTokenValid(manager, user, "ResetPassword", token);
}
public static TokenValidity IsTokenValid<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user, string purpose, string token) where TKey : IEquatable<TKey> where TUser : class, IUser<TKey>
{
try
{
//not sure if this is needed??
if (!(manager.UserTokenProvider is DataProtectorTokenProvider<TUser, TKey> tokenProvider)) return TokenValidity.ERROR;
var unprotectedData = tokenProvider.Protector.Unprotect(Convert.FromBase64String(token));
var ms = new MemoryStream(unprotectedData);
using (var reader = ms.CreateReader())
{
var creationTime = reader.ReadDateTimeOffset();
var expirationTime = creationTime + tokenProvider.TokenLifespan;
var userId = reader.ReadString();
if (!String.Equals(userId, Convert.ToString(user.Id, CultureInfo.InvariantCulture)))
{
return TokenValidity.INVALID;
}
var purp = reader.ReadString();
if (!String.Equals(purp, purpose))
{
return TokenValidity.INVALID;
}
var stamp = reader.ReadString();
if (reader.PeekChar() != -1)
{
return TokenValidity.INVALID;
}
var expectedStamp = "";
//if supported get security stamp for user
if (manager.SupportsUserSecurityStamp)
{
expectedStamp = manager.GetSecurityStamp(user.Id);
}
if (!String.Equals(stamp, expectedStamp)) return TokenValidity.INVALID;
if (expirationTime < DateTimeOffset.UtcNow)
{
return TokenValidity.INVALID_EXPIRED;
}
return TokenValidity.VALID;
}
}
catch
{
// Do not leak exception
}
return TokenValidity.INVALID;
}
}
internal static class StreamExtensions
{
internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
public static BinaryReader CreateReader(this Stream stream)
{
return new BinaryReader(stream, DefaultEncoding, true);
}
public static BinaryWriter CreateWriter(this Stream stream)
{
return new BinaryWriter(stream, DefaultEncoding, true);
}
public static DateTimeOffset ReadDateTimeOffset(this BinaryReader reader)
{
return new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
}
public static void Write(this BinaryWriter writer, DateTimeOffset value)
{
writer.Write(value.UtcTicks);
}
}
}
所以现在我可以添加这个检查:
if (UserManager.IsResetPasswordTokenValid(user, model.Code) == TokenValidity.INVALID_EXPIRED)
{
return this.BadRequest("errorResetingPassword", "Link expired");
}
我的问题是:
1.有更简单的方法吗?
我的目的是向用户显示电子邮件中的链接已过期的信息,因为他现在可以看到的是重置密码存在问题。
2.如果没有构建这样做的方法,那么潜在的安全漏洞是什么? 我使用我的扩展方法作为附加检查。 如果我的方法返回true,我仍然使用ResetPasswordAsync
。
UserManager具有您可以使用的VerifyUserTokenAsync和VerifyUserToken方法。
请参阅Wouter对“我如何检查密码重置令牌是否过期?”这一问题的回答。 更多细节。
所以你可以使用类似的东西
if (!UserManager.VerifyUserToken(userId, "ResetPassword", model.code)){
return this.BadRequest("errorResetingPassword", "Link expired");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.