.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
相关产品推荐
相关产品推荐

