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
相关产品推荐
相关产品推荐

