Asp.NET Identity 2给出“无效令牌”错误


72

我正在使用Asp.Net-Identity-2,并且尝试使用以下方法来验证电子邮件验证代码。但是我收到“无效令牌”错误消息。

  • 我的应用程序的用户管理器是这样的:

    public class AppUserManager : UserManager<AppUser>
    {
        public AppUserManager(IUserStore<AppUser> store) : base(store) { }
    
        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
        {
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
    
            manager.PasswordValidator = new PasswordValidator { 
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };
    
            manager.UserValidator = new UserValidator<AppUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = true,
                RequireUniqueEmail = true
            };
    
            var dataProtectionProvider = options.DataProtectionProvider;
    
            //token life span is 3 hours
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                   new DataProtectorTokenProvider<AppUser>
                      (dataProtectionProvider.Create("ConfirmationToken"))
                   {
                       TokenLifespan = TimeSpan.FromHours(3)
                   };
            }
    
            manager.EmailService = new EmailService();
    
            return manager;
        } //Create
      } //class
    } //namespace
    
  • 我生成令牌的操作是(即使我在此处检查令牌,也会收到“无效令牌”消息):

    [AllowAnonymous]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult ForgotPassword(string email)
    {
        if (ModelState.IsValid)
        {
            AppUser user = UserManager.FindByEmail(email);
            if (user == null || !(UserManager.IsEmailConfirmed(user.Id)))
            {
                // Returning without warning anything wrong...
                return View("../Home/Index");
    
            } //if
    
            string code = UserManager.GeneratePasswordResetToken(user.Id);
            string callbackUrl = Url.Action("ResetPassword", "Admin", new { Id = user.Id, code = HttpUtility.UrlEncode(code) }, protocol: Request.Url.Scheme);
    
            UserManager.SendEmail(user.Id, "Reset password Link", "Use the following  link to reset your password: <a href=\"" + callbackUrl + "\">link</a>");
    
            //This 2 lines I use tho debugger propose. The result is: "Invalid token" (???)
            IdentityResult result;
            result = UserManager.ConfirmEmail(user.Id, code);
        }
    
        // If we got this far, something failed, redisplay form
        return View();
    
    } //ForgotPassword
    
  • 我检查令牌的操作是(这里,当我检查结果时,我总是得到“无效令牌”):

    [AllowAnonymous]
    public async Task<ActionResult> ResetPassword(string id, string code)
    {
    
        if (id == null || code == null)
        {
            return View("Error", new string[] { "Invalid params to reset password." });
        }
    
        IdentityResult result;
    
        try
        {
            result = await UserManager.ConfirmEmailAsync(id, code);
        }
        catch (InvalidOperationException ioe)
        {
            // ConfirmEmailAsync throws when the id is not found.
            return View("Error", new string[] { "Error to reset password:<br/><br/><li>" + ioe.Message + "</li>" });
        }
    
        if (result.Succeeded)
        {
            AppUser objUser = await UserManager.FindByIdAsync(id);
            ResetPasswordModel model = new ResetPasswordModel();
    
            model.Id = objUser.Id;
            model.Name = objUser.UserName;
            model.Email = objUser.Email;
    
            return View(model);
        }
    
        // If we got this far, something failed.
        string strErrorMsg = "";
        foreach(string strError in result.Errors)
        {
            strErrorMsg += "<li>" + strError + "</li>";
        } //foreach
    
        return View("Error", new string[] { strErrorMsg });
    
    } //ForgotPasswordConfirmation
    

我不知道可能遗漏了什么或出了什么问题...

Answers:


72

由于您在此处生成用于重置密码的令牌:

string code = UserManager.GeneratePasswordResetToken(user.Id);

但是实际上尝试验证电子邮件的令牌:

result = await UserManager.ConfirmEmailAsync(id, code);

这是2个不同的令牌。

在您的问题中,您说您正在尝试验证电子邮件,但是您的代码用于密码重置。你在做什么

如果您需要电子邮件确认,请通过生成令牌

var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);

并通过确认

var confirmResult = await UserManager.ConfirmEmailAsync(userId, code);

如果您需要重置密码,请生成以下令牌:

var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);

并确认如下:

var resetResult = await userManager.ResetPasswordAsync(user.Id, code, newPassword);

如果该令牌已经被使用过一次,那么如何使ConfirmEmailAsync返回失败而不是成功。喜欢用户尝试重新访问其电子邮件地址中的链接吗?
user2904995 '19

@ user2904995以使令牌无效,您需要更改SecurityStamp字段。这将使所有先前有效的令牌(包括过去使用过的令牌)无效。
Trailmax

91

我遇到此问题并解决了。有几种可能的原因。

1. URL编码问题(如果问题“随机”发生)

如果随机发生这种情况,则可能会遇到url编码问题。出于未知原因,令牌不是为url安全而设计的,这意味着在通过url传递时,令牌可能包含无效字符(例如,如果通过电子邮件发送)。

在这种情况下,HttpUtility.UrlEncode(token)HttpUtility.UrlDecode(token)应使用。

正如oãoPereira在其评论中所说,UrlDecode不是(有时不是?)是必需的。请尝试两者。谢谢。

2.不匹配的方法(电子邮件与密码令牌)

例如:

    var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);

    var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);

电子邮件令牌提供者生成的令牌无法由重置密码令牌提供者确认。

但是,我们将看到发生这种情况的根本原因。

3.令牌提供者的不同实例

即使您正在使用:

var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);

随着

var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);

错误仍然可能发生。

我的旧代码显示了原因:

public class AccountController : Controller
{
    private readonly UserManager _userManager = UserManager.CreateUserManager(); 

    [AllowAnonymous]
    [HttpPost]
    public async Task<ActionResult> ForgotPassword(FormCollection collection)
    {
        var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", new { area = "", UserId = user.Id, token = HttpUtility.UrlEncode(token) }, Request.Url.Scheme);

        Mail.Send(...);
    }

和:

public class UserManager : UserManager<IdentityUser>
{
    private static readonly UserStore<IdentityUser> UserStore = new UserStore<IdentityUser>();
    private static readonly UserManager Instance = new UserManager();

    private UserManager()
        : base(UserStore)
    {
    }

    public static UserManager CreateUserManager()
    {
        var dataProtectionProvider = new DpapiDataProtectionProvider();
        Instance.UserTokenProvider = new DataProtectorTokenProvider<IdentityUser>(dataProtectionProvider.Create());

        return Instance;
    }

请注意,在此代码中,每次UserManager创建(或new-ed)a时,dataProtectionProvider也会生成一个新的。因此,当用户收到电子邮件并单击链接时:

public class AccountController : Controller
{
    private readonly UserManager _userManager = UserManager.CreateUserManager();
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> ResetPassword(string userId, string token, FormCollection collection)
    {
        var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);
        if (result != IdentityResult.Success)
            return Content(result.Errors.Aggregate("", (current, error) => current + error + "\r\n"));
        return RedirectToAction("Login");
    }

AccountController不再是旧的,也不是_userManager和它的令牌提供商。因此,新令牌提供者将失败,因为它的内存中没有该令牌。

因此,我们需要为令牌提供者使用一个实例。这是我的新代码,可以正常工作:

public class UserManager : UserManager<IdentityUser>
{
    private static readonly UserStore<IdentityUser> UserStore = new UserStore<IdentityUser>();
    private static readonly UserManager Instance = new UserManager();

    private UserManager()
        : base(UserStore)
    {
    }

    public static UserManager CreateUserManager()
    {
        //...
        Instance.UserTokenProvider = TokenProvider.Provider;

        return Instance;
    }

和:

public static class TokenProvider
{
    [UsedImplicitly] private static DataProtectorTokenProvider<IdentityUser> _tokenProvider;

    public static DataProtectorTokenProvider<IdentityUser> Provider
    {
        get
        {

            if (_tokenProvider != null)
                return _tokenProvider;
            var dataProtectionProvider = new DpapiDataProtectionProvider();
            _tokenProvider = new DataProtectorTokenProvider<IdentityUser>(dataProtectionProvider.Create());
            return _tokenProvider;
        }
    }
}

不能说这是一个优雅的解决方案,但它找到了根源并解决了我的问题。


9
这真棒答案的一个注释!:)令牌在作为方法参数接收时,至少在MVC中必须为UrlEncoded,但不应UrlDecoded为,因为它会自动解码。如果再次对其进行解码,由于+字符将被替换为空白,因此会使令牌无效。
若奥·佩雷拉

这没有解决我的问题,因为我需要使用令牌across projects, instances and computers。我AES encryption为此实现了一个自定义,请参阅我的答案以获取详细信息:stackoverflow.com/a/56355282/1216595
塞浦路斯

使用解决方案3解决了Asp Net Core 3.1应用程序中的问题
Krusty

我再次打了这个答案。我上一次使用UserManager的单个实例解决该问题,该实例将消耗UserManager的服务注册为单例。相反,在另一个项目中,如果执行相同的操作,则会引发异常,表明我无法将该服务注册为单例,因为UserManager需要Transient范围。您上面的解决方案无法编译(我可以报告的许多问题)。那么什么可能是有效的解决方案?问题显然是#3(令牌提供者的不同实例)
Krusty

我又修好了。该问题是由于用户表中缺少SecurityStamp列引起的。我已将其删除,但没有该列将不起作用
Krusty

43

即使使用以下代码,我也收到“无效令牌”错误:

var emailCode = UserManager.GenerateEmailConfirmationToken(id);
var result = UserManager.ConfirmEmail(id, emailCode);

在我的情况下,问题变成了是我在手动创建用户和添加他到数据库中,而无需使用UserManager.Create(...)方法。用户存在于数据库中,但没有安全标记。

有趣的是,GenerateEmailConfirmationToken返回的令牌没有抱怨缺少安全标记,但是该令牌永远无法验证。


7
在我的情况下,用户是从旧数据库迁移的,因此具有空的Security Stamps,我运行此程序来修复它:UPDATE AspNetUsers SET SecurityStamp = NewID()
user1069816 2016年

1
我建议使用UPDATE AspNetUsers SET SecurityStamp = NewID() WHERE SecurityStamp is null。就我而言,某些用户的SecurityStamp很好,我希望不要与他们混为一谈。
TNT

要记住的一件事是,身份留给自己的设备,以小写形式生成guid,而NewID()返回大写的guid(至少在SSMS中)。考虑使用LOWER(NewID())
Christopher Berman,

对我来说,实际上是在检查令牌。我通过回购而不是UserManager拉回用户,因此回购用户将其称为ResetPasswordAsync。基本上相同的问题
Yeronimo '19

23

除此之外,我还看到如果不进行编码,代码本身也会失败。

我最近开始以以下方式对我的编码:

string code = manager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);

然后,当我准备读回去时:

string code = IdentityHelper.GetCodeFromRequest(Request);
code = HttpUtility.UrlDecode(code);

老实说,我很惊讶它没有被正确地编码。


5
仅当将其用作重置链接的查询字符串值时,才需要对其进行编码。如果要在应用程序内部提供密码重置表单,而该应用程序会将代码作为隐藏值或类似内容传递,则可以不使用编码就使用它。
埃里克·卡尔森

16

就我而言,我们的AngularJS应用程序将所有加号(+)转换为空白(“”),因此令牌在传递回时确实无效。

为了解决这个问题,在AccountController的ResetPassword方法中,我只是在更新密码之前添加了一个replace:

code = code.Replace(" ", "+");
IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);

我希望这对其他在Web API和AngularJS中使用身份的人有所帮助。


3
对于更正式的方法,我建议var callbackUrl = new Uri(Request.RequestUri, RequestContext.VirtualPathRoot).AbsoluteUri + $"#/resetPassword?username={WebUtility.UrlEncode(user.UserName)}&code={WebUtility.UrlEncode(code)}";正确地对用户名和代码进行url编码到客户端页面(例如Angular),以使用户设置密码并完成请求
Victor

默认令牌是base64编码的,它不是URL安全的,需要URL编码。您可以覆盖或包装令牌提供程序,并改为返回base64url编码的令牌,避免像您已经做的那样避免使用特殊字符。
Bart Verkoeijen

7
string code = _userManager.GeneratePasswordResetToken(user.Id);

                code = HttpUtility.UrlEncode(code);

//发送其余电子邮件


不解码代码

var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password); 

您对解码代码的评论对我不起作用。仅解码代码会成功。
亚伦·休顿

@AaronHudon可能取决于您是通过url字符串还是在请求正文(帖子)中发送它。
Alternatex

1
似乎取决于您使用的是WebAPI还是MVC控制器。MVC控制器URL上的模型活页夹默认将其解码!
Choco '18

6

tl; dr:aspnet core 2.2中注册自定义令牌提供程序以使用AES加密代替MachineKey保护,要点:https : //gist.github.com/cyptus/dd9b2f90c190aaed4e807177c45c3c8b

我遇到了与相同的问题aspnet core 2.2,因为cheny指出令牌提供者的实例需要相同。这对我不起作用,因为

  • 我得到了different API-projects生成令牌并接收令牌以重置密码的密码
  • API可能在different instances虚拟机上运行,因此机器密钥将不同
  • 该API可以restart和令牌将是无效的,因为它不是same instance任何更多

我可以 services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("path")) 用来将令牌保存到文件系统中,并避免重新启动和多个实例共享的问题,但是无法解决多个项目的问题,因为每个项目都会生成一个自己的文件。

对我来说,解决方案是用自己的逻辑替换MachineKey数据保护逻辑,该逻辑确实使用AES then HMAC我自己的设置中的密钥对令牌进行对称加密,我可以在计算机,实例和项目之间共享该密钥。我从“加密”中获取了加密逻辑, 然后在C#中解密了一个字符串? (要点:https : //gist.github.com/jbtule/4336842#file-aesthenhmac-cs)并实现了自定义TokenProvider:

    public class AesDataProtectorTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
    {
        public AesDataProtectorTokenProvider(IOptions<DataProtectionTokenProviderOptions> options, ISettingSupplier settingSupplier)
            : base(new AesProtectionProvider(settingSupplier.Supply()), options)
        {
            var settingsLifetime = settingSupplier.Supply().Encryption.PasswordResetLifetime;

            if (settingsLifetime.TotalSeconds > 1)
            {
                Options.TokenLifespan = settingsLifetime;
            }
        }
    }
    public class AesProtectionProvider : IDataProtectionProvider
    {
        private readonly SystemSettings _settings;

        public AesProtectionProvider(SystemSettings settings)
        {
            _settings = settings;

            if(string.IsNullOrEmpty(_settings.Encryption.AESPasswordResetKey))
                throw new ArgumentNullException("AESPasswordResetKey must be set");
        }

        public IDataProtector CreateProtector(string purpose)
        {
            return new AesDataProtector(purpose, _settings.Encryption.AESPasswordResetKey);
        }
    }
    public class AesDataProtector : IDataProtector
    {
        private readonly string _purpose;
        private readonly SymmetricSecurityKey _key;
        private readonly Encoding _encoding = Encoding.UTF8;

        public AesDataProtector(string purpose, string key)
        {
            _purpose = purpose;
            _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        }

        public byte[] Protect(byte[] userData)
        {
            return AESThenHMAC.SimpleEncryptWithPassword(userData, _encoding.GetString(_key.Key));
        }

        public byte[] Unprotect(byte[] protectedData)
        {
            return AESThenHMAC.SimpleDecryptWithPassword(protectedData, _encoding.GetString(_key.Key));
        }

        public IDataProtector CreateProtector(string purpose)
        {
            throw new NotSupportedException();
        }
    }

和我在项目中使用的SettingsSupplier来提供我的设置

    public interface ISettingSupplier
    {
        SystemSettings Supply();
    }

    public class SettingSupplier : ISettingSupplier
    {
        private IConfiguration Configuration { get; }

        public SettingSupplier(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public SystemSettings Supply()
        {
            var settings = new SystemSettings();
            Configuration.Bind("SystemSettings", settings);

            return settings;
        }
    }

    public class SystemSettings
    {
        public EncryptionSettings Encryption { get; set; } = new EncryptionSettings();
    }

    public class EncryptionSettings
    {
        public string AESPasswordResetKey { get; set; }
        public TimeSpan PasswordResetLifetime { get; set; } = new TimeSpan(3, 0, 0, 0);
    }

最终在Startup中注册提供者:

 services
     .AddIdentity<AppUser, AppRole>()
     .AddEntityFrameworkStores<AppDbContext>()
     .AddDefaultTokenProviders()
     .AddTokenProvider<AesDataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider);


 services.AddScoped(typeof(ISettingSupplier), typeof(SettingSupplier));
//AESThenHMAC.cs: See https://gist.github.com/jbtule/4336842#file-aesthenhmac-cs

4

这是我的工作:为URL编码后解码令牌(简而言之)

首先,我必须对生成的用户GenerateEmailConfirmationToken进行编码。(以上标准建议)

    var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
    var encodedToken = HttpUtility.UrlEncode(token);

并且在您控制器的“确认”操作中,我必须在验证令牌之前对令牌进行解码。

    var decodedCode = HttpUtility.UrlDecode(mViewModel.Token);
    var result = await userManager.ConfirmEmailAsync(user,decodedCode);

2

确保生成时使用:

GeneratePasswordResetTokenAsync(user.Id)

并确认您使用:

ResetPasswordAsync(user.Id, model.Code, model.Password)

如果确定使用的是匹配方法,但仍然无法使用,请验证user.Id两种方法是否相同。(有时您的逻辑可能不正确,因为您允许使用相同的电子邮件进行注册等)


2

在这里,我遇到了同样的问题,但是经过很多时间后,我发现我的自定义Account类具有重新声明和覆盖的Id属性,从而引发了无效令牌错误。

像那样:

 public class Account : IdentityUser
 {
    [ScaffoldColumn(false)]
    public override string Id { get; set; } 
    //Other properties ....
 }

因此,要解决此问题,我只是删除了该属性,然后再次生成数据库架构以确保。

消除这个可以解决问题。


2

以下解决方案在WebApi中为我提供了帮助:

注册

var result = await _userManager.CreateAsync(user, model.Password);

if (result.Succeeded) {
EmailService emailService = new EmailService();
var url = _configuration["ServiceName"];
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = HttpUtility.UrlEncode(token);

// .Net Core 2.1, Url.Action return null
// Url.Action("confirm", "account", new { userId = user.Id, code = token }, protocol: HttpContext.Request.Scheme);
var callbackUrl = _configuration["ServiceAddress"] + $"/account/confirm?userId={user.Id}&code={encodedToken}";
var message = emailService.GetRegisterMailTemplate(callbackUrl, url);

await emailService.SendEmailAsync( model.Email, $"please confirm your registration {url}", message );
}

确认

[Route("account/confirm")]
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> ConfirmEmail(string userId, string code) {
  if (userId == null || code == null) {
    return Content(JsonConvert.SerializeObject( new { result = "false", message = "data is incorrect" }), "application/json");
  }

  var user = await _userManager.FindByIdAsync(userId);
  if (user == null) {
    return Content(JsonConvert.SerializeObject(new { result = "false", message = "user not found" }), "application/json");
  }

  //var decodedCode = HttpUtility.UrlDecode(code);
  //var result = await _userManager.ConfirmEmailAsync(user, decodedCode);

  var result = await _userManager.ConfirmEmailAsync(user, code);

  if (result.Succeeded)
    return Content(JsonConvert.SerializeObject(new { result = "true", message = "ок", token = code }), "application/json");
  else
    return Content(JsonConvert.SerializeObject(new { result = "false", message = "confirm error" }), "application/json");
}

var encodedToken = HttpUtility.UrlEncode(token);魔术很棒
sairfan

1

也许这是一个旧线程,但就这种情况而言,我一直在随机出现此错误,以至于头疼。我一直在检查所有线程并验证每个建议,但是-似乎随机地-某些代码以“无效令牌”的形式返回。在查询用户数据库后,我终于发现那些“无效令牌”错误与用户名中的空格或其他非字母数字字符直接相关。然后很容易找到解决方案。只需将UserManager配置为允许用户名中包含这些字符。可以在用户管理器创建事件之后立即执行此操作,添加新的UserValidator设置以用以下方式将相应的属性设为false:

 public static UserManager<User> Create(IdentityFactoryOptions<UserManager<User>> options, IOwinContext context)
    {
        var userManager = new UserManager<User>(new UserStore());

        // this is the key 
        userManager.UserValidator = new UserValidator<User>(userManager) { AllowOnlyAlphanumericUserNames = false };


        // other settings here
        userManager.UserLockoutEnabledByDefault = true;
        userManager.MaxFailedAccessAttemptsBeforeLockout = 5;
        userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(1);

        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            userManager.UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("ASP.NET Identity"))
            {
                TokenLifespan = TimeSpan.FromDays(5)
            };
        }

        return userManager;
    }

希望这可以帮助像我这样的“迟到者”!


关于编码/解码,以避免空间,我使用的这个建议其他simbols干扰的作品就像一个魅力:stackoverflow.com/questions/27535233/...
JoeCool

1

确保您生成的令牌不会很快过期-我已将其更改为10秒以进行测试,并且该令牌始终会返回错误。

    if (dataProtectionProvider != null) {
        manager.UserTokenProvider =
           new DataProtectorTokenProvider<AppUser>
              (dataProtectionProvider.Create("ConfirmationToken")) {
               TokenLifespan = TimeSpan.FromHours(3)
               //TokenLifespan = TimeSpan.FromSeconds(10);
           };
    }

1

我们遇到了这种情况,一组用户都可以正常工作。我们已将其隔离到赛门铁克的电子邮件保护系统中,该系统使用安全链接替换了发给用户的电子邮件中的链接,这些链接进入其站点进行验证,然后将用户重定向到我们发送的原始链接。

问题在于,他们正在引入解码...他们似乎在生成的链接上进行URL编码,以将我们的链接作为查询参数嵌入到其站点,但是当用户单击并单击clicksafe.symantec.com时,会将其解码解码他们需要编码的第一部分,还解码我们查询字符串的内容,然后解码浏览器重定向到的URL,然后我们回到特殊字符弄乱了后面代码中查询字符串处理的状态。


1

由于@cheny发布的解决方案#3的影响,我意识到,如果您使用相同的UserManager实例,则生成的代码将被接受。但是在实际情况下,在用户单击电子邮件链接之后,验证代码会在第二个API调用中发生。这意味着UserManager已创建的新实例,并且无法验证由首次调用的第一个实例生成的代码。使其起作用的唯一方法是确保SecurityStamp数据库用户表中具有该列。注册使用UserManageras单例的UserManager类会在应用程序启动时引发异常,因为该类会自动使用Scoped生存期进行注册


0

就我而言,我只需要在发送电子邮件之前执行HttpUtility.UrlEncode。重置期间没有HttpUtility.UrlDecode。


0

与chenny的3.相关。令牌提供者的不同实例

以我为例 IDataProtectionProvider.Create为例,每次调用它时,一个新的guid,这会阻止现有的代码在后续的Web api调用中被识别(每个请求都创建自己的用户管理器)。

使字符串静态化对我来说解决了。

private static string m_tokenProviderId = "MyApp_" + Guid.NewGuid().ToString();
...
manager.UserTokenProvider =
  new DataProtectorTokenProvider<User>(
  dataProtectionProvider.Create(new string[1] { m_tokenProviderId } ))
  {
      TokenLifespan = TimeSpan.FromMinutes(accessTokenLifespan)
  };

0

使用asp.net核心解决此问题,经过大量挖掘,我意识到我已经在Startup中启用了此选项:

services.Configure<RouteOptions>(options =>
{
    options.LowercaseQueryStrings = true;
});

当然,这会使查询字符串中的令牌无效。


0

万一有人遇到这个问题,事实证明令牌不是URL友好的,因此我不得不将其包装在HttpUtility.UrlEncode()中,如下所示:

var callback = Url.Content($"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}/reset-password?token={HttpUtility.UrlEncode(token)}&email={user.Email}");

-1

我的问题是电子邮件中包含ConfirmationToken的拼写错误:

<p>Please confirm your account by <a href=@ViewBag.CallbackUrl'>clicking here</a>.</p>

这意味着多余的撇号将附加到ConfirmationToken的末尾。

天哪!


-1

我的问题是我<input asp-for="Input.Code" type="hidden" />在“重置密码”表单中缺少控件

<form role="form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Input.Code" type="hidden" />
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.