如何避免ClaimsTransformerService在用户登录/刷新时多次执行?
Blazor Server中IClaimsTransformation重复执行的解决方法
在Blazor Server(.NET 6)结合Windows身份验证时,IClaimsTransformation的TransformAsync方法多次执行是常见现象,主要原因是应用启动过程中会触发多个请求(比如页面预渲染、SignalR连接、静态资源加载等),每个请求都会触发Claims转换流程。以下是几种有效的解决思路:
1. 检查已存在的Claim,跳过重复处理
在转换逻辑中先判断目标Claim是否已添加,若存在则直接返回原ClaimsPrincipal,避免重复执行数据库查询和事件记录:
namespace MyServerApp.Services { public class ClaimsTransformerService : IClaimsTransformation { private readonly IUserService _userService; private readonly ILoginRecordService _loginRecordService; public ClaimsTransformerService(IUserService userService, ILoginRecordService loginRecordService) { _userService = userService; _loginRecordService = loginRecordService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var identity = principal.Identity as ClaimsIdentity; // 检查是否已添加目标角色Claim,避免重复处理 if (identity?.HasClaim(ClaimTypes.Role, "MyUser") == true) { return principal; } var username = principal.Identity?.Name?.ToLower(); if (string.IsNullOrEmpty(username)) { return principal; } var user = await _userService.GetUser(username); if (user != null) { // 避免重复记录登录事件:检查当日是否已有记录 var hasTodayRecord = await _loginRecordService.HasLoginRecordToday(username); if (!hasTodayRecord) { await _loginRecordService.LogLoginEvent(username); } identity?.AddClaim(new Claim(ClaimTypes.Role, "MyUser")); } return principal; } } }
2. 过滤非核心请求
通过IHttpContextAccessor获取当前请求路径,排除静态资源、SignalR hub等不需要处理Claims的请求,减少无效调用:
首先在Program.cs注册IHttpContextAccessor:
builder.Services.AddHttpContextAccessor();
修改ClaimsTransformerService:
namespace MyServerApp.Services { public class ClaimsTransformerService : IClaimsTransformation { private readonly IUserService _userService; private readonly ILoginRecordService _loginRecordService; private readonly IHttpContextAccessor _httpContextAccessor; public ClaimsTransformerService(IUserService userService, ILoginRecordService loginRecordService, IHttpContextAccessor httpContextAccessor) { _userService = userService; _loginRecordService = loginRecordService; _httpContextAccessor = httpContextAccessor; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var context = _httpContextAccessor.HttpContext; if (context != null) { var path = context.Request.Path; // 排除静态文件、SignalR框架路径 if (path.StartsWithSegments("/_framework") || path.StartsWithSegments("/signalr") || path.StartsWithSegments("/css") || path.StartsWithSegments("/js")) { return principal; } } // 后续处理逻辑同方法1... } } }
3. 缓存已处理用户的状态
使用内存缓存记录已完成Claims转换的用户,避免重复执行数据库操作:
在Program.cs注册内存缓存:
builder.Services.AddMemoryCache();
修改ClaimsTransformerService:
namespace MyServerApp.Services { public class ClaimsTransformerService : IClaimsTransformation { private readonly IUserService _userService; private readonly ILoginRecordService _loginRecordService; private readonly IMemoryCache _cache; public ClaimsTransformerService(IUserService userService, ILoginRecordService loginRecordService, IMemoryCache cache) { _userService = userService; _loginRecordService = loginRecordService; _cache = cache; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var username = principal.Identity?.Name?.ToLower(); if (string.IsNullOrEmpty(username)) { return principal; } var cacheKey = $"ClaimsTransform:{username}"; // 检查缓存,已处理则直接返回 if (_cache.TryGetValue(cacheKey, out bool hasProcessed) && hasProcessed) { return principal; } var identity = principal.Identity as ClaimsIdentity; var user = await _userService.GetUser(username); if (user != null) { var hasTodayRecord = await _loginRecordService.HasLoginRecordToday(username); if (!hasTodayRecord) { await _loginRecordService.LogLoginEvent(username); } identity?.AddClaim(new Claim(ClaimTypes.Role, "MyUser")); // 缓存1小时,可根据需求调整过期时间 _cache.Set(cacheKey, true, TimeSpan.FromHours(1)); } return principal; } } }
以上三种方法可结合使用,优先用方法1(检查Claim)来避免重复逻辑,再配合方法2或3进一步减少不必要的执行。
内容的提问来源于stack exchange,提问作者Iraj
相关产品推荐
相关产品推荐

