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

ASP.NET Core:如何替代IClaimsTransformation实现AAD令牌声明持久化?

在ASP.NET Core中持久化自定义声明的替代方案

针对你遇到的IClaimsTransformation每次授权都重复执行、重复查询数据库的问题,以下是几种更高效的替代方案:


方案1:在AAD令牌验证时一次性添加声明(推荐)

利用Cookie认证的OnTokenValidated事件,在用户首次登录验证AAD令牌时,从数据库获取自定义声明并添加到认证票据中,后续请求直接从Cookie读取包含完整声明的ClaimsPrincipal,无需重复操作。

代码实现

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// 配置Cookie认证事件
builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.Events.OnTokenValidated = async context =>
    {
        var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (!string.IsNullOrEmpty(userId))
        {
            // 从数据库查询当前用户的自定义声明
            var customClaims = await GetCustomClaimsFromDatabase(userId);
            
            var identity = context.Principal.Identity as ClaimsIdentity;
            foreach (var claim in customClaims)
            {
                // 避免重复添加相同声明
                if (!identity.HasClaim(c => c.Type == claim.Type && c.Value == claim.Value))
                {
                    identity.AddClaim(claim);
                }
            }
            
            // 更新认证票据,将声明持久化到Cookie
            context.Properties.IsPersistent = true; // 根据业务需求设置会话是否持久化
            context.Success();
        }
    };
});

优势

  • 仅在用户首次登录验证令牌时执行一次数据库查询
  • 后续请求直接复用Cookie中存储的完整声明,性能最优
  • 完全符合“一次添加、会话内持久化”的需求

方案2:利用Session缓存自定义声明

通过ASP.NET Core的Session机制,在用户首次认证后将自定义声明存入Session,后续请求优先从Session读取,避免重复查询数据库。

步骤1:配置Session

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置会话超时时间
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// 在中间件管道中启用Session
app.UseSession();

步骤2:自定义中间件处理声明

public class CustomClaimsMiddleware
{
    private readonly RequestDelegate _next;

    public CustomClaimsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.User.Identity.IsAuthenticated)
        {
            // 从Session读取缓存的自定义声明
            var cachedClaims = context.Session.Get<List<Claim>>("UserCustomClaims");
            
            if (cachedClaims == null)
            {
                var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
                cachedClaims = await GetCustomClaimsFromDatabase(userId);
                // 将声明存入Session
                context.Session.Set("UserCustomClaims", cachedClaims);
            }

            // 将声明添加到当前用户身份
            var identity = context.User.Identity as ClaimsIdentity;
            foreach (var claim in cachedClaims)
            {
                if (!identity.HasClaim(c => c.Type == claim.Type && c.Value == claim.Value))
                {
                    identity.AddClaim(claim);
                }
            }
        }

        await _next(context);
    }
}

// 注册中间件
app.UseMiddleware<CustomClaimsMiddleware>();

方案3:给IClaimsTransformation添加缓存逻辑

如果必须使用IClaimsTransformation,可以通过内存缓存(MemoryCache)避免重复查询数据库,仅在缓存过期或不存在时才执行数据库操作。

代码实现

public class CachedClaimsTransformer : IClaimsTransformation
{
    private readonly IMemoryCache _cache;
    private readonly ICustomClaimRepository _claimRepo;
    private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(30);

    public CachedClaimsTransformer(IMemoryCache cache, ICustomClaimRepository claimRepo)
    {
        _cache = cache;
        _claimRepo = claimRepo;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            return principal;
        }

        // 构建缓存键
        var cacheKey = $"CustomClaims_{userId}";
        // 从缓存获取声明,不存在则查询数据库并缓存
        var customClaims = await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _cacheExpiry;
            return await _claimRepo.GetClaimsForUser(userId);
        });

        // 添加声明到当前身份
        var identity = principal.Identity as ClaimsIdentity;
        foreach (var claim in customClaims)
        {
            if (!identity.HasClaim(c => c.Type == claim.Type && c.Value == claim.Value))
            {
                identity.AddClaim(claim);
            }
        }

        return principal;
    }
}

// 注册服务
builder.Services.AddScoped<IClaimsTransformation, CachedClaimsTransformer>();
builder.Services.AddMemoryCache();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:45:06