ASP.NET Core 7.x勾选RememberMachine后amr claim未设为mfa的问题
ASP.NET Core 7.x Identity MFA "Remember Machine" amr 声明问题解决
问题确认
确实有不少开发者遇到过这个问题——这是ASP.NET Core Identity内置逻辑的一个小疏漏:当用户勾选“Remember Machine”完成MFA后,首次登录的amr声明会被设为mfa;但退出后重新登录时,Identity会正确跳过MFA流程,却不会自动更新amr声明,依然保留密码登录的pwd值,导致依赖amr声明的MFA强制授权规则失效。这个问题在MVC和原生Razor Pages模板中都会复现。
无需重写SignInManager的解决方案
可以通过以下两种方式修复,都不需要重写SignInManager:
方案1:使用声明转换(ClaimsTransformation)全局修正
通过声明转换中间件,在每次请求时自动检查是否存在MFA记住设备的Cookie,动态调整amr声明:
- 创建自定义声明转换器:
public class AmrClaimsTransformer : IClaimsTransformation { private readonly IHttpContextAccessor _httpContextAccessor; public AmrClaimsTransformer(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) return Task.FromResult(principal); // 检查是否存在Identity的MFA记住设备Cookie var hasRememberMfaCookie = httpContext.Request.Cookies.ContainsKey("Identity.TwoFactorRememberMe"); if (hasRememberMfaCookie) { var identity = principal.Identity as ClaimsIdentity; if (identity != null) { // 替换pwd类型的amr声明为mfa var existingAmr = identity.FindFirst(ClaimTypes.AuthenticationMethod); if (existingAmr != null && existingAmr.Value == "pwd") { identity.RemoveClaim(existingAmr); identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, "mfa")); } } } return Task.FromResult(principal); } }
- 在
Program.cs中注册服务:
builder.Services.AddScoped<IClaimsTransformation, AmrClaimsTransformer>(); builder.Services.AddHttpContextAccessor();
这种方式会全局生效,覆盖所有请求场景,确保只要用户是通过记住MFA设备登录的,amr声明就会被设为mfa。
方案2:登录回调中手动修正声明
在登录成功的回调逻辑里,检查用户是否通过记住MFA设备跳过了MFA流程,手动更新amr声明:
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { var user = await _userManager.FindByEmailAsync(Input.Email); if (user != null && await _userManager.GetTwoFactorEnabledAsync(user)) { // 检查是否存在MFA记住设备Cookie var hasRememberMfaCookie = HttpContext.Request.Cookies.ContainsKey("Identity.TwoFactorRememberMe"); if (hasRememberMfaCookie) { var identity = User.Identity as ClaimsIdentity; if (identity != null) { // 更新amr声明为mfa var existingAmr = identity.FindFirst(ClaimTypes.AuthenticationMethod); if (existingAmr != null) identity.RemoveClaim(existingAmr); identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, "mfa")); // 更新当前用户的ClaimsPrincipal并刷新登录状态 HttpContext.User = new ClaimsPrincipal(identity); await _signInManager.RefreshSignInAsync(user); } } } // 后续跳转逻辑 return RedirectToLocal(ReturnUrl); }
这种方式更直接,只在登录环节处理,适合对全局中间件修改有顾虑的场景。
内容的提问来源于stack exchange,提问作者Jeff Zola
相关产品推荐
相关产品推荐

