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

PHP实现Remember me功能 读取Cookie哈希密码登录报密码错误

问题根因

自动登录校验失败的直接原因是password_verify()函数使用逻辑错误:

  • 该函数仅用于校验明文密码与密码哈希的匹配关系,要求第一个参数传入用户输入的明文密码,第二个参数传入存储的密码哈希。
  • 你当前存入Cookie的pwd字段是从数据库取出的密码哈希,自动登录时将这个哈希值作为第一个参数传入password_verify(),相当于把哈希值当作"明文"和同一份哈希做匹配,必然永远返回false,触发密码错误提示。

除此之外,原设计思路本身存在不可接受的安全风险:即使修改为直接比对Cookie与数据库中的密码哈希是否相等,一旦Cookie被窃取、或者数据库发生拖库,攻击者拿到密码哈希后就可以长期伪造用户身份登录,直到用户主动修改密码,完全不符合安全开发规范。

安全合规的Remember Me实现方案

核心原则:绝对不在Cookie中存储任何与用户密码相关的内容(包括明文、密码哈希),改用一次性随机持久登录令牌实现自动登录,令牌仅用于自动登录校验,和用户密码完全解耦。

前置准备:新建令牌存储表

单独建表存储持久登录令牌,避免和用户账号表耦合,方便令牌的作废、轮换管理:

CREATE TABLE `auth_tokens` (
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int(11) NOT NULL,
  `token_hash` varchar(255) NOT NULL,
  `expires_at` datetime NOT NULL,
  FOREIGN KEY (`user_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字段说明:

  • user_id:关联accounts表的用户ID,用户删除时自动级联删除对应令牌
  • token_hash:存储令牌的哈希值,即使数据库泄露,攻击者也无法拿到可用的原始令牌
  • expires_at:令牌过期时间,过期令牌自动失效

第一步:修改登录成功后的令牌下发逻辑(authenticate.php)

用户校验通过、勾选记住我选项时,执行以下逻辑替换原有存密码哈希到Cookie的代码:

  1. 生成密码学安全的高熵随机令牌,禁止用时间戳、弱随机函数生成
  2. 对令牌做哈希后存入数据库,绑定用户ID和过期时间
  3. 将用户标识(邮箱)、原始未哈希的令牌写入Cookie,同时开启HttpOnly属性禁止JS读取,HTTPS站点需额外开启Secure属性
    替换代码片段:
// 原有校验密码通过、初始化会话的逻辑保留,替换原来的setcookie存pwd部分
if($_POST['remember'] == 1 || $_POST['remember'] == "on") {
    // 生成32字节安全随机令牌
    $rawToken = bin2hex(random_bytes(32));
    $expireTime = time() + 86400 * 30; // 30天有效期
    $expireDt = date('Y-m-d H:i:s', $expireTime);
    // 哈希令牌后存入数据库
    $tokenHash = password_hash($rawToken, PASSWORD_DEFAULT);
    $tokenStmt = $con->prepare('INSERT INTO auth_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
    $tokenStmt->bind_param('iss', $id, $tokenHash, $expireDt);
    $tokenStmt->execute();
    $tokenStmt->close();
    // 写入Cookie,最后一个参数true开启HttpOnly,HTTPS站点请将第6个参数设为true开启Secure
    setcookie('remember_user', $_POST['email'], $expireTime, "/", "", false, true);
    setcookie('remember_token', $rawToken, $expireTime, "/", "", false, true);
}

第二步:修改首页自动登录校验逻辑(index.php)

页面初始化时按优先级校验:先判断是否已有有效登录会话,有则直接跳转首页;没有会话再检测记住我Cookie做自动登录校验,校验流程为:

  1. 读取记住我Cookie,根据用户标识查询对应用户信息
  2. 查询该用户名下未过期的令牌记录
  3. 校验Cookie中携带的原始令牌与数据库存储的令牌哈希是否匹配
  4. 校验通过则初始化登录会话,可选做令牌轮换(作废旧令牌、下发新令牌)后跳转首页
  5. 校验失败则清除无效Cookie,展示正常登录表单
    替换index.php开头的PHP逻辑代码:
<?php
session_start();
// 已登录直接跳转首页
if(isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === TRUE) {
    header('Location: php/home.php');
    exit;
}

$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = '';
$DATABASE_NAME = 'webproject1';
$con = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME);
if (mysqli_connect_errno()) {
    exit('Failed to connect to MySQL: ' . mysqli_connect_error());
}

// 检测记住我Cookie
if(isset($_COOKIE['remember_user'], $_COOKIE['remember_token'])) {
    $rememberEmail = $_COOKIE['remember_user'];
    $rememberToken = $_COOKIE['remember_token'];

    if($stmt = $con->prepare('SELECT id FROM accounts WHERE email = ?')) {
        $stmt->bind_param('s', $rememberEmail);
        $stmt->execute();
        $stmt->store_result();
        if($stmt->num_rows > 0) {
            $stmt->bind_result($id);
            $stmt->fetch();
            $stmt->close();

            // 查询用户未过期的令牌
            $tokenStmt = $con->prepare('SELECT id, token_hash FROM auth_tokens WHERE user_id = ? AND expires_at > NOW() LIMIT 1');
            $tokenStmt->bind_param('i', $id);
            $tokenStmt->execute();
            $tokenStmt->store_result();
            if($tokenStmt->num_rows > 0) {
                $tokenStmt->bind_result($tokenId, $tokenHash);
                $tokenStmt->fetch();
                $tokenStmt->close();
                // 校验令牌匹配
                if(password_verify($rememberToken, $tokenHash)) {
                    // 初始化登录会话
                    session_regenerate_id();
                    $_SESSION['loggedin'] = TRUE;
                    $_SESSION['name'] = $rememberEmail;
                    $_SESSION['id'] = $id;

                    // 令牌轮换:删除旧令牌,生成新令牌,降低令牌泄露风险
                    $delStmt = $con->prepare('DELETE FROM auth_tokens WHERE id = ?');
                    $delStmt->bind_param('i', $tokenId);
                    $delStmt->execute();
                    $delStmt->close();
                    $newToken = bin2hex(random_bytes(32));
                    $newExpire = time() + 86400*30;
                    $newExpireDt = date('Y-m-d H:i:s', $newExpire);
                    $newTokenHash = password_hash($newToken, PASSWORD_DEFAULT);
                    $newTokenStmt = $con->prepare('INSERT INTO auth_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
                    $newTokenStmt->bind_param('iss', $id, $newTokenHash, $newExpireDt);
                    $newTokenStmt->execute();
                    $newTokenStmt->close();
                    setcookie('remember_user', $rememberEmail, $newExpire, "/", "", false, true);
                    setcookie('remember_token', $newToken, $newExpire, "/", "", false, true);

                    header('Location: php/home.php');
                    exit;
                }
            }
            $tokenStmt->close();
        } else {
            $stmt->close();
        }
    }
    // 校验失败,清除无效Cookie
    setcookie('remember_user', '', time() - 3600, "/");
    setcookie('remember_token', '', time() - 3600, "/");
}
?>
<!-- 原有登录页HTML代码保留即可 -->

第三步:补充令牌作废逻辑

在以下场景必须对应用户的有效令牌做删除处理,避免被盗令牌被恶意使用:

  • 用户主动点击退出登录时,清除当前Cookie对应的令牌,或清除该用户所有持久令牌
  • 用户修改密码、重置密码时,清除该用户名下所有持久登录令牌
    退出逻辑参考代码:
<?php
session_start();
// 数据库连接逻辑省略
if(isset($_COOKIE['remember_user'], $_COOKIE['remember_token'])) {
    // 可选择删除对应用户所有令牌,或仅删除当前Cookie对应的令牌
    setcookie('remember_user', '', time() - 3600, "/");
    setcookie('remember_token', '', time() - 3600, "/");
}
session_destroy();
header('Location: index.php');
exit;
?>

安全注意事项

  • 所有持久令牌必须设置合理有效期,禁止生成永久有效令牌
  • Cookie必须开启HttpOnly属性,HTTPS站点必须开启Secure属性,建议设置SameSite=Lax/Strict,降低XSS、CSRF风险
  • 令牌必须使用random_bytes等密码学安全的随机数生成函数生成,禁止使用时间戳、uniqid()等弱随机源
  • 数据库中仅存储令牌的哈希值,不存原始令牌

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:00:57