仿Facebook长期登录功能故障:部分用户莫名被登出求排查
问题分析与修复方案
看起来你的长期登录功能出现了几个关键逻辑漏洞,导致部分用户被意外强制登出,我来逐一拆解问题并给出修复方案:
1. 登录时未更新已有Session记录
在login函数中,当检测到用户已有session记录时直接跳转首页,没有同步更新session表中的token和User Agent。这会导致:如果用户Cookie过期后重新登录,新Cookie的token和session表中留存的旧token不匹配,checkToken校验时就会判定无效,强制用户登出。
2. Cookie设置缺少核心参数
你调用setcookie时只指定了名称、值和过期时间,没有设置path和domain。默认情况下Cookie的作用域是当前目录,一旦网站页面分布在不同路径下(比如/admin和/index),Cookie可能无法被跨路径读取,导致checkToken检测不到Cookie而触发登出。
3. Session表查询逻辑有误
checkToken中通过username查询所有session记录,再用foreach取最后一条的token做对比。如果用户有多设备登录记录,或者session表中存在多条该用户的记录,就会用非当前设备的token做校验,导致合法用户被强制登出。
4. 删除Cookie时参数不匹配
强制登出时的setcookie('token', null)没有匹配创建Cookie时的path、domain等参数,可能导致旧Cookie无法被彻底删除,残留的无效Cookie会引发后续校验异常。
修复后的代码
修复版login函数
public static function login($conn) { $user = filter_var($_POST['username'], FILTER_SANITIZE_STRING); $pass = filter_var($_POST['password'], FILTER_SANITIZE_STRING); // 验证账号密码 $query = $conn->prepare("select * from users where username= ? and password = ?"); $query->execute([$user, $pass]); if ($query->rowCount() > 0) { $row = $query->fetch(PDO::FETCH_ASSOC); // 单用户无需循环,直接取第一条记录 $username = $row['username']; $token = funcs::getToken(10); $userAgent = $_SERVER['HTTP_USER_AGENT']; $cookieExpires = time() + (365 * 24 * 60 * 60); // 设置Cookie时指定全局路径、域名,开启HttpOnly增强安全性 setcookie('token', $token, $cookieExpires, '/', $_SERVER['HTTP_HOST'], false, true); setcookie('username', $username, $cookieExpires, '/', $_SERVER['HTTP_HOST'], false, true); // 检查用户Session记录:有则更新,无则插入 $getTokenQuery = $conn->prepare("select * from session where username= ?"); $getTokenQuery->execute([$username]); if ($getTokenQuery->rowCount() == 0) { $insertQuery = $conn->prepare("insert into session (username, session, agent) values (?,?,?)"); $insertQuery->execute([$username, $token, $userAgent]); } else { // 替换直接跳转逻辑,更新已有Session的token和设备信息 $updateQuery = $conn->prepare("update session set session = ?, agent = ? where username = ?"); $updateQuery->execute([$token, $userAgent, $username]); } header('Location: /index'); exit; } return true; }
修复版checkToken函数
public static function checkToken($conn) { // 快速校验Cookie是否存在 if (!isset($_COOKIE['token']) || !isset($_COOKIE['username'])) { self::logout(); exit; } $username = $_COOKIE['username']; $token = $_COOKIE['token']; $userAgent = $_SERVER['HTTP_USER_AGENT']; // 精准查询匹配当前用户、token和设备的Session记录 $query = $conn->prepare("select * from session where username = ? and session = ? and agent = ?"); $query->execute([$username, $token, $userAgent]); if ($query->rowCount() == 0) { self::logout(); exit; } // 校验通过,可按需初始化Session $_SESSION['username'] = $username; $_SESSION['token'] = $token; } // 统一登出逻辑,确保Cookie删除参数与创建时一致 private static function logout() { $cookieExpires = time() - 3600; // 设为1小时前强制过期 setcookie('token', null, $cookieExpires, '/', $_SERVER['HTTP_HOST'], false, true); setcookie('username', null, $cookieExpires, '/', $_SERVER['HTTP_HOST'], false, true); // 销毁Session(如果使用了Session) if (session_status() === PHP_SESSION_ACTIVE) { session_destroy(); } header("Location: /login"); }
额外优化建议
- 给
session表的username字段添加唯一索引,避免同一用户产生多条无效记录 - 如果需要支持多设备同时登录,可去掉登录时的更新逻辑,改为允许同一用户存在多条Session记录,此时
checkToken只需通过username + token查询即可 - 网站启用HTTPS后,将
setcookie的第6个参数(secure)设为true,确保Cookie仅通过HTTPS传输
内容的提问来源于stack exchange,提问作者Ezaldeen Ezaldeen
相关产品推荐
相关产品推荐

