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

如何避免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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 20:35:15