如何追踪ASP.NET Core Identity缓存登录的用户连接事件?
捕获Blazor Server+ASP.NET Core Identity的用户会话认证事件
针对你的场景(Blazor Server架构、ASP.NET Core Identity Cookie认证),要追踪用户每次连接的会话记录(含时间、IP),可以通过以下几种方式结合实现,覆盖主动登录和Cookie自动登录的场景:
1. 利用Cookie认证事件拦截自动登录/主动登录
ASP.NET Core Identity的Cookie认证提供了事件钩子,可在Cookie验证或用户登录时触发记录逻辑:
配置CookieAuthenticationEvents
在Program.cs中配置Identity的Cookie选项,添加OnValidatePrincipal(处理Cookie自动验证)和OnSigningIn(处理主动登录)事件:
builder.Services.ConfigureApplicationCookie(options => { options.Events = new CookieAuthenticationEvents { // 每次Cookie验证通过时触发(包括用户通过Cookie自动登录) OnValidatePrincipal = async context => { if (context.Principal?.Identity?.IsAuthenticated == true) { var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString(); var currentTime = DateTime.UtcNow; // 获取自定义的会话跟踪服务 var trackingService = context.HttpContext.RequestServices .GetRequiredService<IUserTrackingService>(); // 可选:避免短时间内重复记录(比如间隔30分钟以上才记录新会话) var lastTrackedTime = await trackingService.GetLastTrackedTimeAsync(userId); if (lastTrackedTime == null || currentTime - lastTrackedTime > TimeSpan.FromMinutes(30)) { await trackingService.TrackUserSessionAsync(userId, currentTime, remoteIp); } } }, // 用户主动登录时触发(比如输入账号密码登录) OnSigningIn = async context => { var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString(); var currentTime = DateTime.UtcNow; var trackingService = context.HttpContext.RequestServices .GetRequiredService<IUserTrackingService>(); await trackingService.TrackUserSessionAsync(userId, currentTime, remoteIp); } }; });
2. 监听Blazor Server的SignalR连接事件
Blazor Server基于SignalR通信,用户每次打开应用、刷新页面都会建立新的SignalR连接,可在Hub中重写OnConnectedAsync方法记录会话:
自定义Blazor Hub
创建自定义Hub并重写连接事件:
using Microsoft.AspNetCore.SignalR; using System.Security.Claims; public class AppHub : Hub { private readonly IUserTrackingService _trackingService; private readonly IHttpContextAccessor _httpContextAccessor; public AppHub(IUserTrackingService trackingService, IHttpContextAccessor httpContextAccessor) { _trackingService = trackingService; _httpContextAccessor = httpContextAccessor; } public override async Task OnConnectedAsync() { var user = Context.User; if (user?.Identity?.IsAuthenticated == true) { var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); // 获取远程IP:优先从初始HTTP请求的HttpContext获取, fallback到SignalR连接的IP var remoteIp = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString() ?? Context.Connection.RemoteIpAddress?.ToString(); var currentTime = DateTime.UtcNow; // 同样添加重复记录校验 var lastTrackedTime = await trackingService.GetLastTrackedTimeAsync(userId); if (lastTrackedTime == null || currentTime - lastTrackedTime > TimeSpan.FromMinutes(30)) { await _trackingService.TrackUserSessionAsync(userId, currentTime, remoteIp); } } await base.OnConnectedAsync(); } }
注册必要服务
在Program.cs中注册IHttpContextAccessor(用于Hub中获取初始HTTP请求的IP)和自定义服务:
// 注册HttpContext访问器 builder.Services.AddHttpContextAccessor(); // 注册会话跟踪服务 builder.Services.AddScoped<IUserTrackingService, UserTrackingService>(); // 注册自定义Hub(如果Blazor Server未自动注册默认Hub) builder.Services.AddSignalR().AddHubOptions<AppHub>(options => { // 可选配置Hub选项 });
3. 实现会话跟踪服务
定义用于存储会话记录的实体和服务:
会话日志实体
using Microsoft.AspNetCore.Identity; public class UserSessionLog { public int Id { get; set; } public string UserId { get; set; } = string.Empty; public DateTime SessionStartTime { get; set; } public string? RemoteIpAddress { get; set; } // 关联Identity用户 public IdentityUser? User { get; set; } }
跟踪服务接口与实现
using Microsoft.EntityFrameworkCore; public interface IUserTrackingService { Task TrackUserSessionAsync(string userId, DateTime timestamp, string? remoteIp); Task<DateTime?> GetLastTrackedTimeAsync(string userId); } public class UserTrackingService : IUserTrackingService { private readonly ApplicationDbContext _dbContext; public UserTrackingService(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task TrackUserSessionAsync(string userId, DateTime timestamp, string? remoteIp) { var log = new UserSessionLog { UserId = userId, SessionStartTime = timestamp, RemoteIpAddress = remoteIp }; _dbContext.UserSessionLogs.Add(log); await _dbContext.SaveChangesAsync(); } public async Task<DateTime?> GetLastTrackedTimeAsync(string userId) { return await _dbContext.UserSessionLogs .Where(l => l.UserId == userId) .OrderByDescending(l => l.SessionStartTime) .Select(l => l.SessionStartTime) .FirstOrDefaultAsync(); } }
更新DbContext
在你的ApplicationDbContext中添加DbSet:
public DbSet<UserSessionLog> UserSessionLogs { get; set; }
然后执行迁移创建对应的数据库表。
关键说明
- 重复记录避免:通过检查用户上次记录的时间间隔,避免短时间内(比如用户频繁刷新页面)产生大量重复记录,可根据业务需求调整时间阈值。
- IP获取:Blazor Server中SignalR连接的IP可能需要从初始HTTP请求的
HttpContext获取,因为SignalR升级后的连接IP可能不准确,所以结合IHttpContextAccessor和Context.Connection.RemoteIpAddress两种方式。 - 场景覆盖:Cookie事件覆盖主动登录和Cookie自动验证场景,Hub连接事件覆盖用户重新打开应用、刷新页面的场景,两者结合可以完整追踪用户的所有会话连接。
内容的提问来源于stack exchange,提问作者David Thielen
相关产品推荐
相关产品推荐

