.NET 6 Blazor Server中AspNet Core Identity自定义Claim丢失问题
解决方案:Blazor Server中自定义Claim频繁丢失问题
问题根源
自定义Claim频繁丢失的核心原因是:登录时通过SignInWithClaimsAsync添加的是临时Claim,并未持久化到用户数据中;而RevalidatingIdentityAuthenticationStateProvider在重新验证身份时,会通过UserManager重新生成ClaimsPrincipal,这个新Principal仅包含数据库中存储的持久化Claims,临时添加的Claim不会被保留。
可行解决方案
方案1:将自定义Claim持久化到数据库
在登录时,不仅临时添加Claim,还将其保存到用户的Claims表中,确保每次重新验证身份时,UserManager会自动读取该Claim并加入到ClaimsPrincipal。
修改Login.cshtml.cs的登录逻辑:
public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); if (!ModelState.IsValid) return Page(); try { var adLoginResult = ADHelper.ADLogin(Input.Username, Input.Password); var employeeTimeClaim = new Claim("EmployeeTimeId", adLoginResult.TimeId); // 检查用户是否已存在该Claim,避免重复添加 var existingClaim = await _userManager.GetClaimsAsync(user) .FirstOrDefault(c => c.Type == "EmployeeTimeId"); if (existingClaim == null) { await _userManager.AddClaimAsync(user, employeeTimeClaim); } await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, new[] { employeeTimeClaim }); return LocalRedirect(returnUrl); } catch (Exception ex) { ModelState.AddModelError(string.Empty, $"Login Failed. Error: {ex.Message}."); return Page(); } }
方案2:自定义ClaimsPrincipalFactory动态添加Claim
创建自定义的UserClaimsPrincipalFactory,在生成ClaimsPrincipal的过程中动态获取并添加自定义Claim,确保任何场景下(登录、重新验证)生成的身份都包含该Claim。
- 创建自定义Factory类:
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser, IdentityRole> { public CustomUserClaimsPrincipalFactory( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor) { } public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user) { var principal = await base.CreateAsync(user); // 从AD获取用户的EmployeeTimeId var adUserInfo = ADHelper.GetUserInfo(user.UserName); if (adUserInfo != null) { var identity = (ClaimsIdentity)principal.Identity; // 移除旧Claim避免重复 var existingClaim = identity.FindFirst("EmployeeTimeId"); if (existingClaim != null) { identity.RemoveClaim(existingClaim); } identity.AddClaim(new Claim("EmployeeTimeId", adUserInfo.TimeId)); } return principal; } }
- 在
Program.cs中注册该Factory:
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, CustomUserClaimsPrincipalFactory>();
方案3:修改Revalidation逻辑保留自定义Claim
调整RevalidatingIdentityAuthenticationStateProvider的验证逻辑,在验证通过后更新AuthenticationState时,保留原有的自定义Claim。
修改ValidateAuthenticationStateAsync方法:
protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken) { var scope = _scopeFactory.CreateScope(); try { var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>(); var isValid = await ValidateSecurityTimeStampAsync(userManager, authenticationState.User); if (isValid) { var user = await userManager.GetUserAsync(authenticationState.User); if (user != null) { var newPrincipal = await userManager.CreateUserPrincipalAsync(user); var identity = (ClaimsIdentity)newPrincipal.Identity; // 从原身份中提取并保留自定义Claim var originalEmployeeClaim = authenticationState.User.FindFirst("EmployeeTimeId"); if (originalEmployeeClaim != null) { var existingClaim = identity.FindFirst("EmployeeTimeId"); if (existingClaim != null) identity.RemoveClaim(existingClaim); identity.AddClaim(originalEmployeeClaim); } NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(newPrincipal))); } } return isValid; } finally { if(scope is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { scope.Dispose(); } } }
推荐方案
优先选择方案2,它能确保在任何场景下动态获取最新的自定义Claim,同时避免数据库持久化带来的冗余数据问题,适配从外部系统(如AD)动态获取用户信息的场景。
内容的提问来源于stack exchange,提问作者Ash K
相关产品推荐
相关产品推荐

