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

如何在ASP.NET Core中用缓存阻止并发登录?找不到Global.asax该怎么处理

ASP.NET Core 实现单用户单会话(替代传统Global.asax方案)

首先明确:ASP.NET Core 中没有 Global.asax——它采用模块化的中间件和依赖注入架构,替代了传统ASP.NET的全局事件模型。下面把你参考的「禁止并发登录」方案适配到ASP.NET Core中,同时翻译原方案的核心逻辑:


原参考方案核心逻辑(翻译自2008年的文章)

在传统ASP.NET里,开发者会在Global.asax的Session_Start事件中做以下操作:

  1. 获取当前登录用户的用户名(通过FormsAuthentication)
  2. 在Application全局状态中维护「用户名-活跃会话ID」的映射
  3. 当检测到用户已有活跃会话时,调用Session.Abandon()销毁旧会话,强制用户只能保持一个登录状态

ASP.NET Core 适配实现步骤

Core里没有Application全局状态,我们用分布式缓存(替代Application)+ 身份验证事件(替代Global.asax事件)来实现,以下是具体代码:

1. 注册必要服务(Program.cs)

var builder = WebApplication.CreateBuilder(args);

// 添加分布式缓存(开发用内存缓存,生产建议用Redis/SQL Server缓存)
builder.Services.AddDistributedMemoryCache();

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

// 配置Cookie身份验证,并添加会话检查逻辑
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Events = new CookieAuthenticationEvents
        {
            // 用户登录时,检查并替换旧会话
            OnSigningIn = async context =>
            {
                var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
                var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();

                // 获取用户已有的活跃会话ID
                var existingSessionId = await cache.GetStringAsync($"ActiveSession_{userId}");
                if (!string.IsNullOrEmpty(existingSessionId))
                {
                    // 标记旧会话为无效
                    await cache.RemoveAsync($"SessionValid_{existingSessionId}");
                }

                // 存储当前会话ID为用户的活跃会话
                var currentSessionId = context.HttpContext.Session.Id;
                await cache.SetStringAsync($"ActiveSession_{userId}", currentSessionId);
                await cache.SetStringAsync($"SessionValid_{currentSessionId}", "true", new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
                });
            },

            // 每次请求验证会话有效性
            OnValidatePrincipal = async context =>
            {
                var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
                if (userId == null) return;

                var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
                var currentSessionId = context.HttpContext.Session.Id;

                // 检查当前会话是否是用户的活跃会话
                var activeSessionId = await cache.GetStringAsync($"ActiveSession_{userId}");
                var isSessionValid = await cache.GetStringAsync($"SessionValid_{currentSessionId}");

                if (activeSessionId != currentSessionId || isSessionValid != "true")
                {
                    // 会话无效,强制注销
                    context.RejectPrincipal();
                    await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                }
            },

            // 用户注销时清理缓存
            OnSigningOut = async context =>
            {
                var userId = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier);
                if (userId == null) return;

                var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
                var currentSessionId = context.HttpContext.Session.Id;

                await cache.RemoveAsync($"ActiveSession_{userId}");
                await cache.RemoveAsync($"SessionValid_{currentSessionId}");
            }
        };
    });

// 添加MVC等其他服务
builder.Services.AddControllersWithViews();

2. 启用中间件(Program.cs)

要确保中间件顺序正确,Session必须在Authentication之前:

var app = builder.Build();

// ...(其他中间件,比如静态文件)

app.UseSession(); // 先启用Session
app.UseAuthentication(); // 再启用身份验证
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

注意事项

  • 生产环境缓存选择:不要用AddDistributedMemoryCache,改用AddStackExchangeRedisCache或AddSqlServerCache,避免应用重启后会话数据丢失
  • 扩展功能:可以修改逻辑支持多会话限制(比如允许用户同时登录2个设备),只需调整缓存中存储的会话ID列表即可
  • 会话超时:确保缓存过期时间和Session超时时间保持一致,避免出现无效会话残留

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 04:15:29