如何防止同一账号多地同时登录(限制并发 Session)?

张开发
2026/4/19 2:27:43 15 分钟阅读

分享文章

如何防止同一账号多地同时登录(限制并发 Session)?
限制同一账号多地同时登录从原理到实战的全方位指南1. 引言共享单车与账号共享的“冲突”2. 前置知识为什么需要限制并发登录2.1 HTTP 的无状态与会话机制2.2 并发登录的风险3. 核心方案如何限制并发登录3.1 方案一Session Redis 映射传统 Session 架构原理流程代码示例Java Redis 伪代码3.2 方案二Token 版本号无状态 JWT 方案原理流程代码示例Java 伪代码3.3 方案三登录记录 最大并发数多端管理原理流程4. 方案对比5. 实现要点与注意事项5.1 分布式环境必须使用集中存储5.2 踢出与拒绝的选择5.3 避免并发竞态5.4 移动端与多端区分5.5 主动清理过期数据6. 常见误区7. 总结从需求到落地的核心步骤1. 引言共享单车与账号共享的“冲突”想象一下你买了一辆共享单车的月卡。你骑车时朋友也想用同一张月卡扫码。如果系统允许那么你们可以同时使用——这对你来说是“共享”对公司来说却是“损失”。为了防止这种情况共享单车平台会限制同一账号在同一时间只能在一辆车上使用。在 Web 应用中这种“一账号多设备同时登录”同样会带来安全风险账号被共享、被盗后无法及时发现、难以审计。本文将带你深入理解如何实现“限制同一账号多地同时登录”并掌握从 Session 到 Token 的多种技术方案。2. 前置知识为什么需要限制并发登录2.1 HTTP 的无状态与会话机制HTTP 协议是无状态的——服务器不会记住两次请求之间的关系。为了让服务器认出“你是谁”我们引入了Session或Token机制。用户登录后服务端生成一个凭证Session ID 或 JWT客户端后续请求携带它服务端据此识别用户。2.2 并发登录的风险如果没有限制同一个账号可以在多个设备、多个浏览器上同时登录这会带来账号共享多人共用同一账号违反用户协议安全风险账号被盗后攻击者可同时登录用户无法及时发现审计困难无法区分不同用户的操作难以追溯责任因此限制同一账号多地同时登录是保障系统安全和业务合规的重要手段。3. 核心方案如何限制并发登录3.1 方案一Session Redis 映射传统 Session 架构原理登录时将userId → sessionId的映射存入 Redis。新登录时根据旧的sessionId销毁旧 Session并更新映射。每次请求拦截校验当前 Session 对应的userId是否与 Redis 中存储的最新sessionId一致。流程是否用户登录Redis中已有该userId的sessionId?根据旧sessionId销毁旧Session删除旧映射创建新Session将userId:新sessionId存入Redis登录成功代码示例Java Redis 伪代码// 登录逻辑publicvoidlogin(Stringusername,Stringpassword){// 1. 验证用户UseruseruserService.verify(username,password);// 2. 获取旧的 sessionIdStringoldSessionIdredis.get(user:session:user.getId());if(oldSessionId!null){// 3. 使旧 Session 失效sessionManager.invalidate(oldSessionId);redis.del(user:session:user.getId());}// 4. 创建新 SessionHttpSessionnewSessionrequest.getSession();StringnewSessionIdnewSession.getId();// 5. 存储新映射redis.set(user:session:user.getId(),newSessionId,3600);}// 拦截器校验publicbooleanpreHandle(HttpServletRequestrequest){HttpSessionsessionrequest.getSession(false);if(sessionnull)returnfalse;StringsessionIdsession.getId();LonguserId(Long)session.getAttribute(userId);StringstoredSessionIdredis.get(user:session:userId);// 如果当前 sessionId 不是最新的则踢出if(!sessionId.equals(storedSessionId)){session.invalidate();returnfalse;}returntrue;}优点与现有 Session 机制无缝集成实现简单。缺点依赖 Redis增加架构复杂度。3.2 方案二Token 版本号无状态 JWT 方案原理在用户表中维护一个token_version字段。每次登录时生成新 JWT 时将该版本号编码进 Token并递增用户表的版本号。服务端验证 Token 时比对 Token 中的版本号是否与数据库一致。流程用户登录服务端生成 JWT将token_version放入 payload。更新用户表中的token_version1。下次请求时服务端解析 JWT取出其中的token_version与数据库中的版本号比对。若不一致说明已有新登录当前 Token 失效。代码示例Java 伪代码// 登录publicStringlogin(Stringusername,Stringpassword){UseruseruserService.verify(username,password);// 更新版本号user.setTokenVersion(user.getTokenVersion()1);userService.update(user);// 生成 JWT将版本号放入 payloadStringjwtJwts.builder().setSubject(user.getId().toString()).claim(version,user.getTokenVersion()).signWith(SignatureAlgorithm.HS256,secretKey).compact();returnjwt;}// 拦截器校验publicbooleanverify(Stringtoken){ClaimsclaimsJwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();LonguserIdLong.parseLong(claims.getSubject());IntegertokenVersionclaims.get(version,Integer.class);UseruseruserService.getById(userId);if(!tokenVersion.equals(user.getTokenVersion())){thrownewTokenExpiredException(账号已在其他设备登录);}returntrue;}优点无状态无需 Redis适合微服务架构。缺点JWT 无法主动吊销依赖数据库查询版本号增加了数据库访问。3.3 方案三登录记录 最大并发数多端管理原理存储userId → [device1, device2, ...]的映射允许同一账号在多个设备登录但限制最大并发数如最多 3 个。新设备登录时如果超过限制则踢出最早登录的设备。流程每个登录生成唯一deviceId可基于 User-Agent IP 随机数。将deviceId存入 Redis 的user:devices:{userId}有序集合按登录时间排序。新登录时如果集合大小超过限制删除最早的元素并通知对应设备下线。优点灵活可区分设备类型PC、手机、平板允许用户管理已登录设备。缺点实现复杂需要维护设备标识和推送下线通知。4. 方案对比方案存储依赖无状态主动踢出多端管理实现复杂度Session RedisRedis❌✅❌中Token 版本号数据库✅✅❌中登录记录 并发数Redis❌✅✅高5. 实现要点与注意事项5.1 分布式环境必须使用集中存储如果使用 Session 方案必须用 Redis 替代本地内存否则不同节点无法共享 Session 映射。如果使用 Token 版本号数据库必须是共享的。5.2 踢出与拒绝的选择新登录踢旧用户体验好用户不必每次重新登录适合大多数场景。旧在线时拒绝新登录更严格适合安全要求高的系统如银行、内部系统。5.3 避免并发竞态同一用户短时间内多次登录如网络重试可能导致多次踢出操作。需加锁如 Redis 分布式锁保证原子性。5.4 移动端与多端区分如果需要 PC 和 App 同时在线可在映射 key 中加入设备类型user:session:{userId}:{deviceType}。5.5 主动清理过期数据Redis 中的映射应设置 TTL如 Session 超时时间防止“僵尸映射”堆积。6. 常见误区误区正解“依赖 Session 过期时间就够了”❌ 仅靠超时无法实现主动踢出必须维护映射并主动销毁旧会话。“JWT 无状态无法限制并发”✅ 可以通过版本号实现但需数据库配合。“只在登录时踢一次就完事”⚠️ 旧会话可能仍有效如刷新页面需在请求拦截器中持续校验。“分布式环境下 Session 自动共享”❌ 默认 Session 不共享必须用 Redis 等集中存储。7. 总结从需求到落地的核心步骤步骤关键动作1. 确定需求选择“踢旧”还是“拒新”是否支持多端最大并发数多少2. 选择方案Session 架构用 Redis 映射无状态架构用 Token 版本号。3. 存储映射用 Redis 存储userId → sessionId或userId → deviceList。4. 主动销毁新登录时根据映射找到旧会话并销毁session.invalidate()或删除 Token 记录。5. 持续校验每个请求拦截器中校验当前凭证是否为最新。6. 处理并发加锁防止竞态设置 TTL 避免内存泄漏。一句话总结限制同一账号多地登录的核心是建立用户与活跃会话的映射并在每次请求中验证凭证的“新鲜度”。无论选择哪种技术方案都需考虑分布式一致性、并发安全与用户体验的平衡。

更多文章