Jul*_*urt 59 c# asp.net asp.net-mvc-5 asp.net-identity-2
我正在使用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
Run Code Online (Sandbox Code Playgroud)生成令牌的我的行动是(即使我在这里检查令牌,我得到"无效令牌"消息):
[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
Run Code Online (Sandbox Code Playgroud)我检查令牌的行为是(在这里,当我检查结果时,我总是得到"无效令牌"):
[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
Run Code Online (Sandbox Code Playgroud)我不知道可能会遗漏什么或者什么是错的......
che*_*eny 81
我遇到了这个问题并解决了它.有几个可能的原因.
如果这是随机发生的,您可能会遇到网址编码问题.由于未知原因,令牌不是为url-safe设计的,这意味着它在通过url传递时可能包含无效字符(例如,如果通过电子邮件发送).
在这种情况下,HttpUtility.UrlEncode(token)
并HttpUtility.UrlDecode(token)
应使用.
正如奥雷·佩雷拉在评论中所说,UrlDecode
不是(有时不是?).请试试.谢谢.
例如:
var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
Run Code Online (Sandbox Code Playgroud)
和
var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
Run Code Online (Sandbox Code Playgroud)
由reset-password-token-provider无法确认由email-token-provide生成的令牌.
但我们会看到导致这种情况发生的根本原因.
即使你使用:
var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
Run Code Online (Sandbox Code Playgroud)
随着
var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);
Run Code Online (Sandbox Code Playgroud)
错误仍然可能发生.
我的旧代码说明了原因:
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(...);
}
Run Code Online (Sandbox Code Playgroud)
和:
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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,在此代码中,每次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");
}
Run Code Online (Sandbox Code Playgroud)
它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;
}
Run Code Online (Sandbox Code Playgroud)
和:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
它不能称为优雅的解决方案,但它触及了根本并解决了我的问题.
tra*_*max 57
因为您在此处为密码重置生成令牌:
string code = UserManager.GeneratePasswordResetToken(user.Id);
Run Code Online (Sandbox Code Playgroud)
但实际上尝试验证电子邮件的令牌:
result = await UserManager.ConfirmEmailAsync(id, code);
Run Code Online (Sandbox Code Playgroud)
这些是2种不同的代币.
在您的问题中,您说您正在尝试验证电子邮件,但您的代码是用于重置密码.你在做哪一个?
如果您需要确认电子邮件,请生成令牌
var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
Run Code Online (Sandbox Code Playgroud)
并通过确认
var confirmResult = await UserManager.ConfirmEmailAsync(userId, code);
Run Code Online (Sandbox Code Playgroud)
如果需要密码重置,请生成如下标记:
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
Run Code Online (Sandbox Code Playgroud)
并确认如下:
var resetResult = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
Run Code Online (Sandbox Code Playgroud)
men*_*del 39
即使使用以下代码,我也会收到"无效令牌"错误:
var emailCode = UserManager.GenerateEmailConfirmationToken(id);
var result = UserManager.ConfirmEmail(id, emailCode);
Run Code Online (Sandbox Code Playgroud)
在我的情况下,问题结果是我手动创建用户并将他添加到数据库而不使用该UserManager.Create(...)
方法.用户存在于数据库中但没有安全标记.
有趣的是,GenerateEmailConfirmationToken
返回的令牌没有抱怨缺少安全标记,但该令牌永远无法验证.
LTM*_*MOD 20
除此之外,我已经看到代码本身如果没有编码就会失败.
我最近开始以下列方式编码我的:
string code = manager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);
Run Code Online (Sandbox Code Playgroud)
然后当我准备好回读它时:
string code = IdentityHelper.GetCodeFromRequest(Request);
code = HttpUtility.UrlDecode(code);
Run Code Online (Sandbox Code Playgroud)
说实话,我很惊讶它首先没有被正确编码.
小智 15
在我的例子中,我们的AngularJS应用程序将所有加号(+)转换为空格(""),因此令牌在传回时确实无效.
要解决此问题,在AccountController中的ResetPassword方法中,我只是在更新密码之前添加了替换:
code = code.Replace(" ", "+");
IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);
Run Code Online (Sandbox Code Playgroud)
我希望这可以帮助其他任何在Web API和AngularJS中使用Identity的人.
tl;dr:在aspnet core 2.2 中注册自定义令牌提供程序以使用 AES 加密而不是 MachineKey 保护,要点:https : //gist.github.com/cyptus/dd9b2f90c190aaed4e807177c45c3c8b
我遇到了同样的问题aspnet core 2.2
,因为 cheny 指出令牌提供程序的实例需要相同。这对我不起作用,因为
different API-projects
哪个生成令牌并接收令牌以重置密码different instances
虚拟机上运行,因此机器密钥不会相同restart
和令牌将是无效的,因为它不是same instance
任何更多我可以使用
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("path"))
将令牌保存到文件系统并避免重新启动和多个实例共享问题,但无法解决多个项目的问题,因为每个项目都会生成一个自己的文件。
我的解决方案是用自己的逻辑替换 MachineKey 数据保护逻辑,该逻辑确实用于使用AES then HMAC
我自己的设置中的密钥对令牌进行对称加密,我可以在机器、实例和项目之间共享。我从Encrypt 中获取了加密逻辑
并在 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
以及我在项目中使用的 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);
}
Run Code Online (Sandbox Code Playgroud)
最后在 Startup 中注册提供程序:
services
.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<AesDataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider);
services.AddScoped(typeof(ISettingSupplier), typeof(SettingSupplier));
Run Code Online (Sandbox Code Playgroud)
//AESThenHMAC.cs: See https://gist.github.com/jbtule/4336842#file-aesthenhmac-cs
Run Code Online (Sandbox Code Playgroud)
小智 7
string code = _userManager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);
Run Code Online (Sandbox Code Playgroud)
//发送休息电子邮件
不解码代码
var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
Run Code Online (Sandbox Code Playgroud)