重置密码,ASP.Net Identity 2.2.0时出现“无效令牌”错误

时间:2015-03-25 21:24:33

标签: c# asp.net asp.net-mvc asp.net-identity-2

我正在开发一个MVC5 ASP.Net应用程序 我正在使用Identity 2.2.0进行身份验证 一切都很好,但由于Invalid Token错误,我无法重设密码 以下是ResetPassword控制器中的Account相关操作。

[AllowAnonymous]
public ActionResult ForgotPassword()
{
    return View();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (!ModelState.IsValid) return View(model);
    ApplicationUser userModel = await UserManager.FindByNameAsync(model.Email);

    if (userModel == null)
        ModelState.AddModelError("", "The user doesn't exist");

    if (userModel != null && !await UserManager.IsEmailConfirmedAsync(userModel.Id))
        ModelState.AddModelError("", "The user email isn't confirmed");

    if (!ModelState.IsValid) return View();

    var user = _userService.GetUserByEmail(model.Email);

    // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
    // Send an email with this link

    string websiteTitle = StaticAssests.WebsiteTitle;
    string emailContext = _settingService.GetValue(SettingNames.ResetPasswordMailFormat);
    string code = await UserManager.GeneratePasswordResetTokenAsync(userModel.Id);
    string callbackUrl = Url.Action("ResetPassword", "Account", new { userId = userModel.Id, code }, Request.Url.Scheme);
    emailContext = emailContext.Replace("{userfullname}", user.FullName);
    emailContext = emailContext.Replace("{websitetitle}", websiteTitle);
    emailContext = emailContext.Replace("{websitedomain}", StaticVariables.WebsiteDomain);
    emailContext = emailContext.Replace("{username}", userModel.UserName);
    emailContext = emailContext.Replace("{resetpasswordurl}", callbackUrl);
    emailContext = emailContext.Replace("{date}", new PersianDateTime(DateTime.Now).ToLongDateTimeString());
    await UserManager.SendEmailAsync(userModel.Id, string.Format("Reset password {0}", websiteTitle), emailContext);
    return RedirectToAction("ForgotPasswordConfirmation", "Account");
}

[AllowAnonymous]
public ActionResult ForgotPasswordConfirmation()
{
    return View();
}
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
    return code == null ? View("Error") : View();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
    if (!ModelState.IsValid) return View(model);
    ApplicationUser userModel = await UserManager.FindByNameAsync(model.Email);
    if (userModel == null)
    {
        ModelState.AddModelError("", "The user doesn't exist");
        return View();
    }
// Invalid Token error
        IdentityResult result = await UserManager.ResetPasswordAsync(userModel.Id, model.Code, model.Password);
        if (result.Succeeded)
        {
            return RedirectToAction("ResetPasswordConfirmation", "Account");
        }
        AddErrors(result);
        return View();
    }

我检查了以下内容:
1.重置电子邮件发送成功。
2. GeneratePasswordResetTokenAsync运行没有任何问题。并且生成的代码与code动作中的ResetPassword参数相同。

IdentityConfig:

public class ApplicationUserManager : UserManager<ApplicationUser, int>
    {
        public ApplicationUserManager(IUserStore<ApplicationUser, int> userStore) : base(userStore)
        {

        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            ApplicationUserManager applicationUserManager = new ApplicationUserManager(new ApplicationUserStore());
            //ApplicationUserManager applicationUserManager = new ApplicationUserManager(context.Get<ApplicationUser>());
            //new ApplicationUserManager(new UserStore<UserModel>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            //applicationUserManager.UserValidator = new UserValidator<UserIdentityModel, int>(applicationUserManager)
            //{
            //  AllowOnlyAlphanumericUserNames = false,
            //  RequireUniqueEmail = true,
            //};
            applicationUserManager.PasswordValidator = new MyMinimumLengthValidator(6);
            applicationUserManager.UserValidator = new MyUserModelValidator();
            applicationUserManager.PasswordHasher = new MyPasswordHasher();

            // Configure user lockout defaults
            applicationUserManager.UserLockoutEnabledByDefault = true;
            applicationUserManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            applicationUserManager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
            // You can write your own provider and plug in here.
            applicationUserManager.RegisterTwoFactorProvider("PhoneCode",
                new PhoneNumberTokenProvider<ApplicationUser, int>
                {
                    MessageFormat = "Your security code is: {0}"
                });
            applicationUserManager.RegisterTwoFactorProvider("EmailCode",
                new EmailTokenProvider<ApplicationUser, int>
                {
                    Subject = "Security code",
                    BodyFormat = "your security code is {0}"
                });

            applicationUserManager.EmailService = new EmailService();
            applicationUserManager.SmsService = new SmsService();
            IDataProtectionProvider dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                applicationUserManager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser, int>(dataProtectionProvider.Create("ASP.NET Identity"));
            }

            return applicationUserManager;
        }
    }

怎么了?

更新
我已将User Id从字符串更改为int。

1 个答案:

答案 0 :(得分:3)

我知道这是一个老问题,但我在两个项目中遇到过这个问题,两次问题完全相同。也许这个答案可能对其他人有帮传递的令牌包含不能在URL字符串中使用而不会导致问题的特殊字符。

将令牌传递给前端UI时,请务必对令牌进行URLEncode,如下所示:

var callbackUrl =
                new Uri(string.Format("{0}/resetpassword?userId={1}&code={2}",
                    ConfigurationManager.AppSettings["websiteUrl"], user.Id,
                    WebUtility.UrlEncode(token)));

当令牌传回后端时,在将令牌传递给密码ResetPassword函数之前解码令牌:

var result = await this.AppUserManager.ResetPasswordAsync(appUser.Id, WebUtility.UrlDecode(resetPasswordBindingModel.ConfirmCode),
             resetPasswordBindingModel.NewPassword);

我遇到此问题的两个项目都在前端运行HTML / MVC / JavaScript,并且验证是通过ASP.NET WebAPI 2.x完成的。

另一个注意事项:由于某些原因,UrlDecode无法正确解码加号,您会在令牌中获得空格而不是&#39; +&#39;。我使用的技巧是使用字符串替换将任何空格转换为+符号。它并不理想,但我没有遇到任何问题。