.NET Identity中GenerateNewTwoFactorRecoveryCodesAsync未替换已有恢复码问题
问题原因分析
根据提供的代码和现象,以下是导致恢复码无法替换的核心原因:
1. EF Core实体跟踪与并发令牌冲突
在OnGetAsync方法中,通过_userManager.GetUserAsync(User)获取的用户实体已被DbContext跟踪。当调用GenerateNewTwoFactorRecoveryCodesAsync时:
store.ReplaceCodesAsync会修改关联的恢复码存储表(如AspNetUserTokens),但如果用户实体的ConcurrencyStamp在获取后发生隐性变化(例如其他后台操作修改了用户数据),后续的UpdateAsync(user)会触发并发检查失败。- 即使代码判断
update.Succeeded为true并返回新码,若UpdateAsync仅返回成功却未实际提交ReplaceCodesAsync的修改(例如并发冲突被静默处理),会导致数据库中旧码未被替换。
2. 恢复码存储实现的逻辑缺陷
从UserManager的代码来看,ReplaceCodesAsync预期是替换所有旧恢复码,但如果底层IUserRecoveryCodeStore的实现存在问题:
- 未批量删除旧码:若
ReplaceCodesAsync仅删除单个旧恢复码而非全部(例如旧版本Identity的实现漏洞),已有多个恢复码时旧码会残留,新码被追加而非替换。 - 事务未包裹操作:
ReplaceCodesAsync和UpdateAsync作为独立操作,若未被事务包裹,当UpdateAsync执行失败时,ReplaceCodesAsync的修改会被回滚,但代码仍返回新码,造成“生成新码但未替换”的假象。
3. 用户实体获取后的状态异常
OnGetAsync中存在潜在空引用bug(当user == null时访问user.Id),虽不直接导致恢复码问题,但可能引发DbContext状态异常:
if (user == null) { return NotFound($"Unable to load user with ID '{user.Id}'."); // 此处user为null,会抛出NullReferenceException }
该异常可能中断DbContext的正常生命周期,导致后续GenerateNewTwoFactorRecoveryCodesAsync的修改未被正确提交。
4. 恢复码哈希逻辑不一致
若CreateTwoFactorRecoveryCode生成的明文码,在ReplaceCodesAsync中存储的哈希值与旧码的哈希逻辑不一致,会导致数据库中旧码无法被正确识别删除,新码虽生成但无法覆盖旧码。
内容的提问来源于stack exchange,提问作者MarchalPT
相关产品推荐
相关产品推荐

