我正在使用Asp.Net-Identity-2,我正在尝试使用以下方法验证电子邮件验证码.但我收到"无效令牌"错误消息.
我的应用程序的用户管理器是这样的:
public class AppUserManager : UserManager{ public AppUserManager(IUserStore store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions options, IOwinContext context) { AppIdentityDbContext db = context.Get (); AppUserManager manager = new AppUserManager(new UserStore (db)); manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; manager.UserValidator = new UserValidator (manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; var dataProtectionProvider = options.DataProtectionProvider; //token life span is 3 hours if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider (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: link"); //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 TaskResetPassword(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: " + ioe.Message + " " }); } 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 += "" + strError + " "; } //foreach return View("Error", new string[] { strErrorMsg }); } //ForgotPasswordConfirmation
我不知道可能会遗漏什么或者什么是错的......
因为您在此处为密码重置生成令牌:
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);
即使使用以下代码,我也会收到"无效令牌"错误:
var emailCode = UserManager.GenerateEmailConfirmationToken(id); var result = UserManager.ConfirmEmail(id, emailCode);
在我的情况下,问题结果是我手动创建用户并将他添加到数据库而不使用该UserManager.Create(...)
方法.用户存在于数据库中但没有安全标记.
有趣的是,GenerateEmailConfirmationToken
返回的令牌没有抱怨缺少安全标记,但该令牌永远无法验证.
除此之外,我已经看到代码本身如果没有编码就会失败.
我最近开始以下列方式编码我的:
string code = manager.GeneratePasswordResetToken(user.Id); code = HttpUtility.UrlEncode(code);
然后当我准备好回读它时:
string code = IdentityHelper.GetCodeFromRequest(Request); code = HttpUtility.UrlDecode(code);
说实话,我很惊讶它首先没有被正确编码.
在我的例子中,我们的AngularJS应用程序将所有加号(+)转换为空格(""),因此令牌在传回时确实无效.
要解决此问题,在AccountController中的ResetPassword方法中,我只是在更新密码之前添加了替换:
code = code.Replace(" ", "+"); IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);
我希望这可以帮助其他任何在Web API和AngularJS中使用Identity的人.
string code = _userManager.GeneratePasswordResetToken(user.Id); code = HttpUtility.UrlEncode(code);
//发送休息电子邮件
不解码代码
var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
我遇到了这个问题并解决了它.有几个可能的原因.
如果这是随机发生的,您可能会遇到网址编码问题.由于未知原因,令牌不是为url-safe设计的,这意味着它在通过url传递时可能包含无效字符(例如,如果通过电子邮件发送).
在这种情况下,HttpUtility.UrlEncode(token)
并HttpUtility.UrlDecode(token)
应使用.
正如奥雷·佩雷拉在评论中所说,UrlDecode
不是(有时不是?).请试试.谢谢.
例如:
var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
和
var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
由reset-password-token-provider无法确认由email-token-provide生成的令牌.
但我们会看到导致这种情况发生的根本原因.
即使你使用:
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)时,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; } } }
它不能称为优雅的解决方案,但它触及了根本并解决了我的问题.