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

.NET MVC 5实现同一账号限2人登录及自动踢人功能问询

实现.NET MVC5中同一账号最多2人登录并踢掉最早用户的方案

这个需求在很多业务场景里都很实用,我来给你拆解具体的实现思路和代码细节,同时解答你提到的几个问题:

一、核心思路:基于Session+缓存追踪登录会话

你提到的“基于Cookie的检查”其实本质是通过Cookie存储的SessionID来标识每个独立的登录实例——每个用户登录后,服务器会给浏览器分配一个唯一的SessionID(存在Cookie里),我们可以通过这个ID来追踪每个账号的登录数量和登录时间,进而实现踢人逻辑。

具体逻辑是:

  • 每个账号登录成功后,把对应的SessionID和登录时间存储到缓存中(内存缓存或分布式缓存)
  • 新用户登录时,检查该账号的有效会话数量,如果超过2个,就移除最早的那个会话ID,并标记它为失效
  • 在每个请求时验证当前SessionID是否在该账号的有效列表中,如果不在(被踢或过期),就强制注销并跳转登录

二、具体代码实现步骤

1. 定义会话模型

先创建一个简单的类来存储会话信息:

public class UserSession
{
    public string SessionId { get; set; }
    public DateTime LoginTime { get; set; }
}

2. 修改登录逻辑(AccountController)

在登录验证通过后,处理会话的添加和踢人逻辑:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null)
        {
            var currentSessionId = Session.SessionID;
            var userSessionCacheKey = $"UserSessions_{user.Id}";
            
            // 从缓存获取该用户的现有会话列表,没有则新建
            var userSessions = MemoryCache.Default.Get(userSessionCacheKey) as List<UserSession> ?? new List<UserSession>();
            
            // 添加当前登录会话
            userSessions.Add(new UserSession 
            { 
                SessionId = currentSessionId, 
                LoginTime = DateTime.Now 
            });
            
            // 超过2个会话时,移除最早的那个并标记为失效
            if (userSessions.Count > 2)
            {
                var oldestSession = userSessions.OrderBy(s => s.LoginTime).First();
                userSessions.Remove(oldestSession);
                
                // 存储失效会话ID,用于后续验证
                var invalidSessionsKey = $"InvalidSessions_{user.Id}";
                var invalidSessions = MemoryCache.Default.Get(invalidSessionsKey) as HashSet<string> ?? new HashSet<string>();
                invalidSessions.Add(oldestSession.SessionId);
                MemoryCache.Default.Set(invalidSessionsKey, invalidSessions, 
                    new CacheItemPolicy { SlidingExpiration = TimeSpan.FromHours(24) });
            }
            
            // 更新缓存,设置24小时滑动过期(用户无操作24小时后会话失效)
            MemoryCache.Default.Set(userSessionCacheKey, userSessions, 
                new CacheItemPolicy { SlidingExpiration = TimeSpan.FromHours(24) });
            
            // 执行ASP.NET Identity登录流程
            await SignInManager.SignInAsync(user, model.RememberMe, shouldLockout: false);
            return RedirectToLocal(returnUrl);
        }
        ModelState.AddModelError("", "用户名或密码错误。");
    }
    return View(model);
}

3. 实现会话验证过滤器

创建一个全局Action过滤器,在每个请求前检查当前会话是否有效:

public class ValidateUserSessionAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var currentUser = filterContext.HttpContext.User;
        if (currentUser.Identity.IsAuthenticated)
        {
            var userId = currentUser.Identity.GetUserId();
            var currentSessionId = filterContext.HttpContext.Session.SessionID;
            var userSessionCacheKey = $"UserSessions_{userId}";
            
            var validSessions = MemoryCache.Default.Get(userSessionCacheKey) as List<UserSession>;
            
            // 检查当前会话是否在有效列表中
            if (validSessions == null || !validSessions.Any(s => s.SessionId == currentSessionId))
            {
                // 判断是被踢出还是会话过期
                var invalidSessionsKey = $"InvalidSessions_{userId}";
                var invalidSessions = MemoryCache.Default.Get(invalidSessionsKey) as HashSet<string>;
                
                var message = invalidSessions?.Contains(currentSessionId) 
                    ? "您的账号已在其他设备登录,您被踢出系统。" 
                    : "会话已过期,请重新登录。";
                
                // 强制注销并跳转登录页
                filterContext.HttpContext.GetOwinContext().Authentication.SignOut();
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
                {
                    { "controller", "Account" },
                    { "action", "Login" },
                    { "message", message }
                });
            }
        }
        base.OnActionExecuting(filterContext);
    }
}

4. 注册全局过滤器

在FilterConfig中注册这个过滤器,让所有请求都经过验证:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        // 添加会话验证过滤器
        filters.Add(new ValidateUserSessionAttribute());
    }
}

5. 注销时清理会话

在Logout方法中移除当前会话的缓存记录,避免无效数据残留:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Logout()
{
    var userId = User.Identity.GetUserId();
    var currentSessionId = Session.SessionID;
    var userSessionCacheKey = $"UserSessions_{userId}";
    
    var userSessions = MemoryCache.Default.Get(userSessionCacheKey) as List<UserSession>;
    if (userSessions != null)
    {
        var sessionToRemove = userSessions.FirstOrDefault(s => s.SessionId == currentSessionId);
        if (sessionToRemove != null)
        {
            userSessions.Remove(sessionToRemove);
            MemoryCache.Default.Set(userSessionCacheKey, userSessions, 
                new CacheItemPolicy { SlidingExpiration = TimeSpan.FromHours(24) });
        }
    }
    
    AuthenticationManager.SignOut();
    return RedirectToAction("Index", "Home");
}

三、关于.NET MVC5的现成解决方案

很遗憾,原生的ASP.NET MVC5和ASP.NET Identity并没有直接提供“限制登录人数+踢人”的功能,需要像上面这样基于现有框架扩展实现。如果是更现代的ASP.NET Core,社区有一些第三方库可以用,但MVC5只能自己动手实现。

四、注意事项

  • 多服务器部署:如果你的系统是多服务器集群,内存缓存(MemoryCache)无法跨服务器共享数据,此时需要改用分布式缓存(比如Redis)来存储会话信息,所有服务器共享同一份缓存数据。
  • 会话过期:缓存设置了滑动过期时间,用户24小时无操作后会话会自动失效,避免缓存堆积。
  • 安全性:SessionID本身是加密存储在Cookie中的,ASP.NET默认会验证Session的有效性,不用担心伪造问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:57:56