You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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。

  1. 创建自定义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;
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:54:51