Asp.NET身份验证2出现“无效令牌”错误

83

我正在使用 Asp.Net-Identity-2,并尝试使用以下方法验证电子邮件验证码。但是我收到了一个"Invalid Token"的错误消息。

  • 我的应用程序用户管理器如下所示:

    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
    
    我不知道可能缺少什么或有什么问题...

我不知道可能缺少什么或有什么问题...

24个回答

3

在使用asp.net core时遇到了这个问题,经过大量的挖掘,我意识到我在启动时打开了这个选项:

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

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


2
太准确了!!!你救了我的一天,谢谢。 - Ahmed Suror

3

我也遇到了同样的问题,但经过很长时间的研究后,我发现在我的情况下,无效令牌错误是由于我的自定义帐户类重新声明并覆盖了Id属性导致的。

像这样:

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

所以为了解决这个问题,我已经删除了那个属性,并再次生成了数据库模式,以确保。删除它就可以解决问题。

2
请确保在生成时使用以下内容:
GeneratePasswordResetTokenAsync(user.Id)

请确认您使用的是:

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

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

1
也许这是一个旧的帖子,但为了防万一,我一直在纠结于此错误的随机发生。我一直在检查所有相关的帖子,并验证每个建议,但是-似乎随机地-一些代码被返回为“无效令牌”。 经过对用户数据库的一些查询,我终于发现那些“无效令牌”错误与用户名中的空格或其他非字母数字字符直接相关。 解决方案很容易找到。只需在用户管理器创建事件之后,添加一个新的UserValidator设置来禁用相应的属性即可允许用户名称中包含这些字符。
 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;
    }

希望这能帮助像我一样“迟到”的人!

关于编码/解码以避免空格和其他符号干扰,我使用了这个提案,它非常有效:https://dev59.com/r14c5IYBdhLWcg3wyMwB#31297879 - JoeCool-Avismón

1

确保生成的令牌不会过快过期 - 我曾将其更改为10秒进行测试,结果总是返回错误。

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

1
我们遇到了一组用户的情况,之前都正常运行。我们已经将问题隔离到 Symantec 的电子邮件保护系统上,该系统将我们发送给用户的链接替换为安全链接,并将用户重定向到其网站进行验证,然后再重定向回我们原始的链接。
问题在于,他们引入了一个解码过程...他们似乎对生成的链接进行 URL 编码,以将我们的链接嵌入其网站的查询参数中,但当用户单击并 clicksafe.symantec.com 解码 URL 时,它会解码他们需要编码的第一部分,但也会解码我们的查询字符串内容,然后浏览器重定向到的 URL 已被解码,我们又回到了特殊字符混乱代码处理查询字符串的状态中。

0

我已经解决了大部分描述提示的“无效令牌”问题。这是我针对Blazor项目的解决方案。核心在于StringExtensions类。

当用户注册电子邮件时生成电子邮件:

user = new IdentityUser { UserName = email, Email = email };
var createUser = await _userManager.CreateAsync(user, password);
 if (createUser.Succeeded)
  {
      var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
      var baseUri = NavMgr.BaseUri;
      var setNewPasswordUri = baseUri + "confirm-password";
      var urlWithParams = StringExtensions.GenerateUrl(token, emailTo, url);
      await SendAsync( urlWithParams  );   // use your own Email solution send the email
  }

电子邮件确认(用户点击邮件中的链接)

@page  "/confirm-email"
 
<h3>Confirm email</h3>
 
@Error

        [Inject]
        UserManager<IdentityUser> UserMgr { get; set; }
 
        [Inject] 
        NavigationManager NavMgr { get; set; }
 
 
        protected override Task OnInitializedAsync()
        {
            var url = NavMgr.Uri;
            Token = StringExtensions.GetParamFromUrl(url, "token");
            Email = StringExtensions.GetParamFromUrl(url, "email");
            log.Trace($"Initialised with email={Email} , token={Token}");
            return ActivateEmailAsync();
        }
 
 
        private async Task ActivateEmailAsync()
        {
            isProcessing = true;
            Error = null;
 
            log.Trace($"ActivateEmailAsync started for {Email}");
            isProcessing = true;
            Error = null;
 
            try
            {
                var user = await UserMgr.FindByEmailAsync(Email);
                if (user != null)
                {
                        if (!string.IsNullOrEmpty(Token))
                        {
                            var result = await UserMgr.ConfirmEmailAsync(user, Token);
                            if (result.Succeeded)
                            {
// Show user , that account is activated
                            }
                            else
                            {
                                foreach (var error in result.Errors)
                                {
                                    Error += error.Description;
                                }
                                log.Error($"Setting new password failed for {Email} due to the: {Error}");
                            }
                        }
                        else
                        {
                            log.Error("This should not happen. Token is null or empty");
                        }
                }
 
            }
            catch (Exception exc)
            {
                Error = $"Activation failed";
            }
            isProcessing = false;
        }

 public static class StringExtensions
    {
        /// <summary>
        /// Encode string to be safe to use it in the URL param
        /// </summary>
        /// <param name="toBeEncoded"></param>
        /// <returns></returns>
        public static string Encode(string toBeEncoded)
        {
            var result = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(toBeEncoded));
            return result;
        }
 
        /// <summary>
        /// Decode from the url safe string the original value
        /// </summary>
        /// <param name="toBeDecoded"></param>
        /// <returns></returns>
        public static string Decode(string toBeDecoded)
        {
            var decodedBytes = WebEncoders.Base64UrlDecode(toBeDecoded);
            var result = Encoding.UTF8.GetString(decodedBytes);
            return result;
        }
 
 
        public static string GenerateUrl(string token, string emailTo, string baseUri, string tokenParamName = "token", string emailParamName = "email")
        {
            var tokenEncoded = StringExtensions.Encode(token);
            var emailEncoded = StringExtensions.Encode(emailTo);
            var queryParams = new Dictionary<string, string>();
            queryParams.Add(tokenParamName, tokenEncoded);
            queryParams.Add(emailParamName, emailEncoded);
            var urlWithParams = QueryHelpers.AddQueryString(baseUri, queryParams);
            return urlWithParams;
        }
 
 
        public static string GetParamFromUrl(string uriWithParams, string paramName)
        {
            var uri = new Uri(uriWithParams, UriKind.Absolute);
            var result = string.Empty;
 
            if (QueryHelpers.ParseQuery(uri.Query).TryGetValue(paramName, out var paramToken))
            {
                var queryToken = paramToken.First();
                result  = StringExtensions.Decode(queryToken);
            }
            return result;
        }
 

0

我在重置密码场景中遇到了无效令牌的问题。根本原因是,我为错误的IndentityUser生成了重置令牌。这在简化代码中很容易发现,但在更复杂的代码中花费了我一些时间来修复它。

我应该使用以下代码:

 var user = await UserMgr.FindByEmailAsync(Model.Email);
 string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);

但是我错误地创建了另一个IndentityUser

 // This is example "How it should not be done"
 var user = await UserMgr.FindByEmailAsync(Model.Email);
 
 user = new IdentityUser { UserName = email, Email = email };  // This must not be her !!!! We need to use user found by UserMgr.FindByEmailAsync(Model.Email);
 string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);

完整的简化代码在这里:

private async Task GenerateResetToken()
{
    var user = await UserMgr.FindByEmailAsync(Model.Email);
    if (user == null)
    {
        Model.Error = "Not registered";
    }
    else
    {
        try
        {
            var _userManager = SignInMgr.UserManager;

            UserMgr.FindByEmailAsync(Model.Email);
            string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
            if (resetToken == null)
            {
                log.Error("Cannot get token from GeneratePasswordResetTokenAsync");
            }
            else
            {
                // Reset token generated. Send email to user
            }

        }
        catch (Exception exc)
        {
            log.Error(exc, $"Password reset failed  due to the {exc.Message}");
        }
    }
}

0
在我的情况下,我只需要在发送电子邮件之前执行HttpUtility.UrlEncode。在重置期间不需要进行HttpUtility.UrlDecode。

0
我遇到了同样的问题,如果我在收到电子邮件后10分钟点击重置链接。DataProtectionTokenProviderOptions -> TokenLifespan 设置为10分钟。
我将 DataProtectionTokenProviderOptions -> TokenLifespan 更改为2小时。
现在,如果我在2小时内点击收到的链接,它就能正常工作。如果我在2小时后点击链接,则会出现无效令牌的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接