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

.NET 6 Blazor应用中如何在认证后为ClaimsPrincipal添加自定义声明?

在.NET Core Blazor应用中一次性添加自定义声明的实现方案

问题背景

  • 已掌握基于声明的授权策略,但官方文档对声明的定义与添加仅提及「创建Identity时由可信方分配声明」
  • 需求:从SQL查询获取自定义声明,在用户认证完成后仅一次性嵌入到ClaimsPrincipal中,避免每次请求都查询数据库,同时兼容API端点和Blazor组件的原生[Authorize]特性
  • 尝试过的方案存在问题:
    • 中间件方案:每次请求都会执行,不符合「仅一次」的要求
    • 自定义AccountClaimsPrincipalFactory方案:注入HTTP依赖导致应用崩溃,且该方法内执行HTTP调用存在其他生命周期问题

方案1:Blazor Server 场景(推荐)

利用Identity的UserClaimsPrincipalFactory,在用户首次认证时一次性从数据库加载声明,生成的ClaimsPrincipal会被缓存复用。

  1. 实现自定义UserClaimsPrincipalFactory
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser>
{
    private readonly ApplicationDbContext _dbContext;

    public CustomUserClaimsPrincipalFactory(UserManager<IdentityUser> userManager, 
        IOptions<IdentityOptions> optionsAccessor,
        ApplicationDbContext dbContext) 
        : base(userManager, optionsAccessor)
    {
        _dbContext = dbContext;
    }

    public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
    {
        // 先获取基础身份信息
        var principal = await base.CreateAsync(user);
        var identity = (ClaimsIdentity)principal.Identity;

        // 从数据库查询自定义声明(替换为你的SQL查询逻辑)
        var userCustomClaims = await _dbContext.UserCustomClaims
            .Where(c => c.UserId == user.Id)
            .ToListAsync();

        // 添加自定义声明到身份对象
        foreach (var claim in userCustomClaims)
        {
            identity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
        }

        return principal;
    }
}
  1. 在Program.cs中替换默认工厂
// 注册自定义声明工厂
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, CustomUserClaimsPrincipalFactory>();

该工厂仅在用户首次登录认证时执行一次,后续请求会复用已生成的ClaimsPrincipal,完全满足「仅一次加载」的需求,且原生[Authorize]特性可直接识别这些声明。


方案2:Blazor WebAssembly 场景

避免在AccountClaimsPrincipalFactory中直接执行HTTP请求,改为在登录成功后一次性获取声明并更新用户身份。

  1. 实现基础自定义AccountClaimsPrincipalFactory
public class CustomAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomAccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) 
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var principal = await base.CreateUserAsync(account, options);
        return principal;
    }
}
  1. 实现自定义AuthenticationStateProvider(用于更新身份状态)
public class CustomAuthenticationStateProvider : ApiAuthorizationStateProvider
{
    public CustomAuthenticationStateProvider(IAccessTokenProvider accessTokenProvider, 
        NavigationManager navigationManager, 
        ILogger<ApiAuthorizationStateProvider> logger) 
        : base(accessTokenProvider, navigationManager, logger)
    {
    }

    // 提供外部更新身份状态的方法
    public void NotifyAuthenticationStateChanged(Task<AuthenticationState> task)
    {
        base.NotifyAuthenticationStateChanged(task);
    }
}
  1. 登录成功后加载并添加声明
    在登录页面的回调方法中(比如LoginCompleted事件)执行:
private async Task OnLoginSuccess()
{
    // 调用API获取自定义声明(替换为你的API端点)
    var httpClient = HttpClientFactory.CreateClient("YourApiClient");
    var customClaims = await httpClient.GetFromJsonAsync<List<ClaimDto>>("api/user/customclaims");

    // 获取当前用户身份
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    var identity = (ClaimsIdentity)user.Identity;

    // 添加自定义声明
    foreach (var claim in customClaims)
    {
        identity.AddClaim(new Claim(claim.Type, claim.Value));
    }

    // 更新认证状态,让Blazor组件感知到声明变化
    ((CustomAuthenticationStateProvider)AuthenticationStateProvider).NotifyAuthenticationStateChanged(
        Task.FromResult(new AuthenticationState(user)));
}
  1. 在Program.cs中注册服务
builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomAccountClaimsPrincipalFactory>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

这种方式确保声明仅在登录成功后加载一次,后续请求复用已更新的ClaimsPrincipal,同时规避了CreateUserAsync中执行HTTP请求的生命周期问题。


核心注意事项

  • Blazor Server场景下,直接通过数据库查询加载声明的方案最稳定,无HTTP调用风险,且完全符合「仅一次加载」的要求
  • Blazor WebAssembly场景下,务必避免在CreateUserAsync中执行异步操作(如HTTP请求),改为登录后回调处理可有效规避依赖注入和生命周期问题
  • 两种方案添加的声明均会被原生[Authorize]特性(包括策略授权)识别,无需额外修改授权逻辑

内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 19:55:17