UserManager 无法更改所有用户帐户的 5%

UserManager is unable to alter 5% of all user accounts

当前项目:

我在迁移数据库时遇到了一个非常棘手的运行ge 问题。旧数据库无法挽救(设计错误,数据输入错误,工程),因此为客户设计和构建了一个全新的系统。

由于数据状态不佳,必须通过 Excel 进行迁移,以便清理数据并对某些字段进行一些复杂的计算和验证。

我以这样一种方式重新导入数据,即对于用户而言,他们的 PasswordHash 和 SecurityStamp 字段为空值(默认设置)。然后我 运行 浏览器中的一个无头脚本(一个 ActionResult 一旦完成就简单地重定向回 /Home/Index)遍历所有用户并为他们分配一个 运行dom GUID 作为密码.目的是向所有人发送密码重置电子邮件。

不幸的是,对于 2,000 多名用户中的 102 名用户,此无头脚本无法更新用户。与此相同,PasswordHash 和 SecurityStamp 保持为空。

此问题现已扩展到已发送的密码重置电子邮件。每个电子邮件中的 link 有效,因为它能够重置所有帐户的 95%,但是对于这 102 个帐户,它完全无法清除、设置或重置密码。我试过调试它,但调试器没有深入到 UserManager(可以理解),所以我看不出它在哪里以及如何失败。

由于 95% 的帐户都运行良好,我唯一能想到的是数据库有问题,以至于 UserManager 无法更新那些特定的行。不幸的是,对用户 table 的仔细检查并未显示 102 个受影响的帐户与任何未受影响的帐户之间存在任何具体差异 - 与弹出的那 102 个帐户不一致。

尝试通过 MSSQL 将两个字段设置为某个值(包括空字符串)没有任何区别。

建议? F运行kly,我不知道此时需要显示什么或我应该问什么进一步的问题。


编辑

这里是威士忌-探戈-狐步舞的大场面,伙计们。

结果是 IS 所有帐户之间的共性...... USERNAMES 都具有非数字、非整数字符.如加号 (+) 或破折号 (-)。问题是,破折号在许多域名中很常见(更不用说电子邮件用户名本身了),而加号广泛用于某些提供商(如 gMail)的加号寻址。

我已确认在用户名的任何部分添加加号或破折号会导致通过 UserManager 进行任何类型的密码设置、删除或重置失败。

我通过选择一个尚未使用其重置的用户来做到这一点 link,更改了他们的用户名,使其带有破折号,尝试(但失败了!)重置密码,然后删除破折号并再次尝试(并成功!)。

很明显用户名中不是 a-z、0-9 和 .导致 UserManager 严重失败。

我不明白这是怎么发生的。我字面意思甚至不能。

我正在寻找这方面的方向和指导,因为现在我的脑海里只不过是我办公室里到处都是的大量小弹片。我需要能够接受用户名中的破折号和加号,而不会破坏整个密码 set/delete/reset 功能。

还请理解这个系统利用了 DotNet 系统——这里发生的一切都超出了我个人接触的范围。任何东西悄悄进入的唯一可能方式是通过 Web 表单输入的普通用户名对其用户名进行了“清理”,这样像破折号和加号之类的东西在用户名数据库字段中实际上并不是这样。

老实说,我不知道我的问题是由于我的设置变化无常(有问题的存储库模式)还是由于我实际所做的事情(不太可能),但我确实想出了一个解决方法。

因为我很注重安全,不喜欢把所有的鸡蛋都放在一个篮子里,所以我永远不会使用第三方登录,例如 Facebook 或 Google。这代表了任何网站都不应该给用户带来负担的单点故障。因此,我已经能够忽略 Identity 2.0 的这个扩展功能。这会影响我的工作吗?没有线索,但此声明仅供参考。

让我找到解决方法的线索是

现在,我的 _userManager 不是 derping 批发,只有它写入数据库的能力是,而且只有当数据库的 UserName 字段包含包含加号或破折号的电子邮件地址时。所以我仍然能够利用 UserManager,我只是不能使用它的保存到数据库的功能。

例如,我原来的重置脚本是这样的:

[HttpPost]
[ValidateAntiForgeryToken]
[ValidateSecureHiddenInputs("ResetId")]
public async Task<ActionResult> Reset(ResetViewModel model) {
  if(!ModelState.IsValid) return View("Reset", model);
  try {
    var reset = await CheckReset(model.ResetId);
    if(!(await _userManager.AddPasswordAsync(reset.UserId, model.NewPassword)).Succeeded) throw new Exception(@"We were able to erase the old password, but were unable to set a new password. Please contact [] with all details for a resolution.");
    await ProcessReset(model.ResetId);
    return RedirectToAction("ResetSuccessful", "Home");
  } catch(Exception e) {
    return View("ResetError", new ResetErrorViewModel(e.Message));
  }
}

失败的是 _userManager.AddPasswordAsync(),它默默无闻 - 没有例外,没有数据库错误消息。它只是未能插入新密码,并且在任何试图使用它来访问用户名中带有加号或破折号的任何帐户的任何东西上都炸毁了它的 cookie。

我修改后的代码是这样的:

[HttpPost]
[ValidateAntiForgeryToken]
[ValidateSecureHiddenInputs("ResetId")]
public async Task<ActionResult> Reset(ResetViewModel model) {
  if(!ModelState.IsValid) return View("Reset", model);
  try {
    var r = await CheckReset(model.ResetId);
    var u = await _unitOfWork.UserRepository.FindByIdAsync(r.UserId);
    new UserMap().ResetPassword(u, _userManager.PasswordHasher.HashPassword(model.NewPassword));
    _unitOfWork.UserRepository.Update(u);
    if(await _unitOfWork.SaveChangesAsync() < 1) throw new Exception(@"We were unable to set a new password. Please contact [] with all details for a resolution.");
    await ProcessReset(r.ResetId);
    return RedirectToAction("ResetSuccessful", "Home");
  } catch(Exception e) {
    return View("ResetError", new ResetErrorViewModel(e.Message));
  }
}

注意到区别了吗?我使用 _unitOfWork 和仅 leverage _userManager 来调用行项目 provide 我使用散列密码 - 我离开将该密码保存到数据库 _unitOfWork,效果非常好!

仅供参考,new UserMap().ResetPassword() 是一个自定义映射 class,由于控制问题,我更喜欢使用它而不是自动映射器。

我已将 _userManager 的利用扩展到用户注册方法,并确认它现在可以在这两种方法中完全发挥作用。