CakePHP 4如何实现禁止同一用户账号同时多点登录
CakePHP 4 基于数据库会话实现单账号单点登录方案
你已经将会话存储到数据库,完全不需要在用户表加锁定标记,直接基于现有会话表做校验即可,天然适配会话自动过期的场景,不会出现账号被死锁的问题。
前置准备:调整会话表结构
默认的CakePHP数据库会话表仅包含id(会话ID)、data(会话序列化内容)、expires(过期时间戳)三个字段,先新增一个user_id字段,字段类型和你用户表的主键类型保持一致,加上普通索引提升查询效率。
不需要改动框架核心的会话读写逻辑,只需要在用户登录成功的节点,把当前用户ID绑定到对应会话记录上即可。
核心实现步骤
- 绑定会话与用户ID的关系
利用Authentication插件自带的Authentication.afterIdentify事件做钩子,用户提交账号密码认证通过后,立刻拿到当前请求的会话ID、已认证的用户ID,执行一次更新操作,把当前会话记录的user_id字段设为对应用户ID即可。 - 有效会话判定规则
判定用户是否存在其他有效登录会话的条件非常明确,不需要自己写复杂的过期逻辑:查询会话表中满足user_id = 待校验用户ID、expires > 当前服务器时间戳、id != 当前请求携带的会话ID三个条件的记录,这些记录就是该用户在其他端的有效登录会话。
注意不要在每一次用户请求都执行这个查询,会增加不必要的数据库开销,只需要在两个节点触发校验即可:- 用户提交登录凭证、认证刚通过的节点
- 已登录用户发起请求时,间隔1-2小时校验一次即可
- 重复登录处理策略
根据业务需求二选一即可:- 后登录挤退前登录:查询到其他有效会话后,直接删除这些会话记录即可。之前登录的用户发起下一次请求时,框架找不到对应有效会话,会自动判定为未登录,跳转到登录页。会话过期的记录要么会被框架自带的会话垃圾回收机制自动清理,要么因为expires小于当前时间不会被判定为有效,完全不需要手动维护状态。
- 已登录则禁止新登录:查询到存在其他有效会话时,直接终止当前登录流程,清空当前登录态,返回「该账号已在其他设备登录,请先退出原设备登录后再尝试」的提示即可。建议加个同IP10分钟内重复登录的白名单,避免用户自己刷新页面、重复提交登录表单被误拦截。
不要尝试在用户表加
is_login之类的标记位实现这个需求,只要遇到用户直接关浏览器不主动登出、会话自动过期、网络异常断连的场景,这个标记位会一直卡在已登录状态,用户无法自行解锁,必须找管理员重置,线上问题会非常多。
核心逻辑示例代码
// 在Application.php中注册认证成功后的事件回调 $eventManager = $this->getEventManager(); $eventManager->on('Authentication.afterIdentify', function ($event, $authService, $authResult) { $currentUserId = $authResult->getData()->id; $currentSession = $this->getRequest()->getSession(); $currentSessionId = $currentSession->id(); $sessionsTable = $this->getTableLocator()->get('Sessions'); // 绑定当前会话和用户ID的关联 $sessionsTable->updateAll( ['user_id' => $currentUserId], ['id' => $currentSessionId] ); // 挤退该用户其他端的有效登录(如果要做禁止新登录,就把这段改成count查询,存在记录则抛出认证异常) $sessionsTable->deleteAll([ 'user_id' => $currentUserId, 'id !=' => $currentSessionId, 'expires >' => time() ]); });
默认的CakePHP会话配置已经开启了过期会话自动垃圾回收,不需要额外写定时任务清理无效会话记录,上线前确认下php.ini或者框架会话配置里的GC概率不是0即可。
内容的提问来源于stack exchange,提问作者ProfDaniBest
相关产品推荐
相关产品推荐

