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

仿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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:06:47