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

时间:2018-09-26 21:18:34

标签: asp.net sql-server asp.net-mvc asp.net-mvc-5

当前项目:

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

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

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

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

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

由于所有帐户中的95%都可以正常工作,所以我唯一能想到的就是数据库出了点问题,因此UserManager无法更新这些特定的行。不幸的是,仔细检查“用户”表并没有发现这102个受影响的帐户和任何未受影响的帐户之间的任何特定差异-与弹出的那102个帐户没有一致性。

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

建议?坦白说,我不知道此时需要显示什么或应该问什么进一步的问题。


编辑

这是一个巨大的威士忌-TANGO-FOXTROT时刻,伙计们。

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

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

我这样做是通过选择尚未使用其重置链接的用户,更改其用户名使其具有破折号,尝试(但失败!)重置密码,然后删除破折号并再次尝试来完成的。 (并成功!)。

很明显,用户名中的任何内容都不是a-z,0-9和。导致UserManager严重失败。

这是怎么回事,超出了我的范围。我字面意思甚至都不能。

我正在寻找关于这方面的指导和指导,因为我的想法只不过是我办公室各处的许多小弹片。我需要能够接受用户名中的破折号和加号,而不会破坏整个密码设置/删除/重置功能。<​​/ p>

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

1 个答案:

答案 0 :(得分:1)

老实说,我不知道我的问题是由于设置的不稳定性(有问题的存储库模式)还是由于我实际上做了的(不太可能)所致,但是我确实想出了解决方法。

由于我非常重视安全性,并且讨厌把所有的东西都放到一个篮子里,因此,我永远不会使用Facebook或Google等第三方登录名。这代表了任何网站都不应负担用户的单点故障。因此,我已经能够忽略Identity 2.0的这一扩展功能。这会影响我的工作吗?毫无头绪,但此声明仅供参考。

给我一​​个解决方法的线索是this post

现在,我的_userManager并没有全部销毁,只有它具有写入数据库的功能,并且只有当数据库的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呼叫订单项,并且仅杠杆 _userManager来使用散列密码提供给我-我将密码的保存留给数据库到_unitOfWork,效果很好!

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

我也将_userManager的使用扩展到了用户注册方法,并确认它现在在两种方法中都可以正常使用。