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

如何在PHP中安全延长用户登录会话时长至一周或一月?

实现PHP长期保持登录状态的安全方案

默认PHP Session关闭浏览器后失效,是因为Session依赖的PHPSESSID Cookie是会话级Cookie(默认无过期时间),浏览器关闭后会自动清除。要实现类似Facebook的长期登录,需要结合持久化Cookie和服务器端令牌存储,核心是用加密令牌替代直接存储用户信息,同时确保全程安全。

安全实现的核心步骤

1. 设计令牌存储表

在数据库中创建用于存储登录令牌的表,比如user_sessions,字段建议包括:

  • id:自增主键
  • user_id:关联用户表的ID
  • token_hash:令牌的哈希值(禁止明文存储令牌)
  • expires_at:令牌过期时间(如7天后)
  • created_at:令牌创建时间
  • user_agent:可选,存储用户代理信息,增加验证维度

2. 登录时生成持久化令牌

当用户登录并勾选“记住我”时,生成高熵随机令牌,哈希后存入数据库,同时设置持久化Cookie:

// 假设用户登录验证通过,已获取$user_id和$username
session_start();

if (isset($_POST['remember_me']) && $_POST['remember_me'] === 'on') {
    // 生成32字节的高熵随机令牌(不可预测)
    $token = bin2hex(random_bytes(32));
    // 用PHP原生密码哈希算法存储令牌(即使数据库泄露,攻击者也无法直接使用)
    $token_hash = password_hash($token, PASSWORD_DEFAULT);
    // 设置令牌7天后过期
    $expires_at = date('Y-m-d H:i:s', time() + 7 * 24 * 60 * 60);

    // 用预处理语句插入数据库,防SQL注入
    $stmt = $pdo->prepare("INSERT INTO user_sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)");
    $stmt->execute([$user_id, $token_hash, $expires_at]);

    // 设置安全的持久化Cookie
    setcookie(
        'remember_token',
        $token,
        [
            'expires' => time() + 7 * 24 * 60 * 60,
            'path' => '/',
            'domain' => $_SERVER['HTTP_HOST'],
            'secure' => true, // 仅HTTPS环境下发送Cookie,生产环境必须开启
            'httponly' => true, // 禁止JavaScript读取Cookie,防范XSS攻击
            'samesite' => 'Lax' // 限制Cookie跨站发送,防范CSRF攻击
        ]
    );
}

// 初始化Session
$_SESSION['user_id'] = $user_id;
$_SESSION['username'] = $username;
// 生成新的Session ID,防止Session固定攻击
session_regenerate_id(true);

3. 每次请求自动恢复登录状态

在所有需要登录的页面开头,先检查Session是否有效;若无效,再检查Cookie中的令牌,验证通过后重建Session:

session_start();

// Session已存在,无需处理
if (isset($_SESSION['user_id'])) {
    // 可选:定期更新Session ID,提升安全性
    if (time() - ($_SESSION['last_activity'] ?? 0) > 30 * 60) {
        session_regenerate_id(true);
        $_SESSION['last_activity'] = time();
    }
    return;
}

// Session无效,检查持久化Cookie
if (isset($_COOKIE['remember_token'])) {
    $token = $_COOKIE['remember_token'];
    $valid_token = false;
    $user_id = null;
    $old_token_hash = '';

    // 查询所有未过期的令牌,逐一验证
    $stmt = $pdo->prepare("SELECT id, user_id, token_hash FROM user_sessions WHERE expires_at > NOW()");
    $stmt->execute();
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if (password_verify($token, $row['token_hash'])) {
            $valid_token = true;
            $user_id = $row['user_id'];
            $old_token_hash = $row['token_hash'];
            break;
        }
    }

    if ($valid_token) {
        // 重建Session
        $_SESSION['user_id'] = $user_id;
        // 从用户表获取用户名(避免直接存敏感信息在Session)
        $stmt = $pdo->prepare("SELECT username FROM users WHERE id = ?");
        $stmt->execute([$user_id]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        $_SESSION['username'] = $user['username'];
        $_SESSION['last_activity'] = time();

        // 生成新令牌并更新(令牌轮换,防止令牌长期有效被泄露)
        $new_token = bin2hex(random_bytes(32));
        $new_token_hash = password_hash($new_token, PASSWORD_DEFAULT);
        $new_expires_at = date('Y-m-d H:i:s', time() + 7 * 24 * 60 * 60);

        // 删除旧令牌,插入新令牌
        $stmt = $pdo->prepare("DELETE FROM user_sessions WHERE token_hash = ?");
        $stmt->execute([$old_token_hash]);
        $stmt = $pdo->prepare("INSERT INTO user_sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)");
        $stmt->execute([$user_id, $new_token_hash, $new_expires_at]);

        // 更新Cookie
        setcookie(
            'remember_token',
            $new_token,
            [
                'expires' => time() + 7 * 24 * 60 * 60,
                'path' => '/',
                'domain' => $_SERVER['HTTP_HOST'],
                'secure' => true,
                'httponly' => true,
                'samesite' => 'Lax'
            ]
        );

        // 生成新Session ID,防止Session固定
        session_regenerate_id(true);
    } else {
        // 无效令牌,清除Cookie并删除数据库中的无效记录
        setcookie('remember_token', '', [
            'expires' => time() - 3600,
            'path' => '/',
            'domain' => $_SERVER['HTTP_HOST'],
            'secure' => true,
            'httponly' => true,
            'samesite' => 'Lax'
        ]);
        if (!empty($old_token_hash)) {
            $stmt = $pdo->prepare("DELETE FROM user_sessions WHERE token_hash = ?");
            $stmt->execute([$old_token_hash]);
        }
    }
}

4. 注销时清理所有登录痕迹

注销不仅要销毁Session,还要删除数据库中的令牌并清除Cookie:

session_start();

// 销毁Session
session_unset();
session_destroy();

// 清除持久化Cookie并删除数据库中的令牌
if (isset($_COOKIE['remember_token'])) {
    $token = $_COOKIE['remember_token'];
    // 删除当前用户的所有令牌(支持用户注销所有设备)
    if (isset($_SESSION['user_id'])) {
        $stmt = $pdo->prepare("DELETE FROM user_sessions WHERE user_id = ?");
        $stmt->execute([$_SESSION['user_id']]);
    } else {
        // 若Session已失效,通过令牌哈希查找并删除
        $stmt = $pdo->prepare("SELECT token_hash FROM user_sessions WHERE expires_at > NOW()");
        $stmt->execute();
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            if (password_verify($token, $row['token_hash'])) {
                $del_stmt = $pdo->prepare("DELETE FROM user_sessions WHERE token_hash = ?");
                $del_stmt->execute([$row['token_hash']]);
                break;
            }
        }
    }

    // 清除Cookie
    setcookie('remember_token', '', [
        'expires' => time() - 3600,
        'path' => '/',
        'domain' => $_SERVER['HTTP_HOST'],
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax'
    ]);
}

header('Location: login.php');
exit;

关键安全注意事项

  • 强制HTTPS:生产环境必须开启HTTPS,否则Cookie可能被中间人拦截,secure属性必须设为true。
  • 令牌不可预测:必须用random_bytes()生成令牌,禁止使用rand()或mt_rand()等弱随机函数。
  • 令牌哈希存储:绝对不能明文存储令牌,用password_hash()和password_verify()处理,即使数据库泄露也无法直接使用令牌。
  • Cookie安全属性:httponly防止XSS窃取Cookie,samesite防范CSRF攻击,这两个属性必须开启。
  • 令牌轮换:每次自动登录时生成新令牌,避免单个令牌长期有效被泄露。
  • 定期清理过期令牌:定时删除数据库中已过期的令牌,减少数据冗余和攻击面。
  • Session固定防护:每次登录或自动登录时调用session_regenerate_id(true),生成新的Session ID。

内容的提问来源于stack exchange,提问作者ramenknight

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 22:40:43