在 PHP 中使用“记住我”功能的正确方法短详细

2022-08-31 01:16:18

在登录系统上工作并尝试实现“记住我”功能。

最近,我对这个主题进行了研究,阅读了一堆文章,帖子,故事,小说,童话故事(这样称呼它们,因为其中一些甚至不包含1行代码,只是大量单词)关于,cookie漏洞,如固定,劫持......等。

并决定实现以下目标

  1. 设置登录尝试之间的时间延迟(以防止暴力攻击)和限制尝试次数
  2. 在几乎每个操作上重新生成会话 ID

我真的对我的主要问题感到困惑:哪种方式是正确的,因为“记住我”功能?使用饼干/会话/数据库?

请解释你对代码的想法。(没有代码,我无法清楚地理解)

详细

目前,我的代码看起来像这样

在登录期间,我使用以下函数来设置cookie和会话

protected function validateUser($userid, $ckey=0, $rememmber=0) {
    session_start();
    session_regenerate_id(true); //this is a security measure
    $_SESSION['user_id'] = $userid;
    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
    if (isset($remember) && $rememmber == 'on') {
        setcookie("user_id", $_SESSION['user_id'], time() + 60 * 60 * 24 * COOKIE_TIME_OUT, "/");
        setcookie("user_key", sha1($ckey), time() + 60 * 60 * 24 * COOKIE_TIME_OUT, "/");
    }
    return true;
}

然后在安全用户页面上,检查是否使用user_id从数据库获取有关用户的所有重要数据user_id

public function protect() {
        session_start();

        /* Secure against Session Hijacking by checking user agent */
        if (isset($_SESSION['HTTP_USER_AGENT'])) {
            if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])) {
                $this->signout();
                exit;
            }
        }

// before we allow sessions, we need to check authentication key - ckey and ctime stored in database

        /* If session not set, check for cookies set by Remember me */
        if (!isset($_SESSION['user_id'])) {
            if (isset($_COOKIE['user_id']) && isset($_COOKIE['user_key'])) {
                /* we double check cookie expiry time against stored in database */

                $cookie_user_id = $_COOKIE['user_id'];
                               $stmt = $this->db->prepare("select `ckey`,`ctime` from `users` where `id` =?") or die($this->db->error);
            $stmt->bind_param("i", $cookie_user_id) or die(htmlspecialchars($stmt->error));
            $stmt->execute() or die(htmlspecialchars($stmt->error));
            $stmt->bind_result($ckey, $ctime) or die($stmt->error);
            $stmt->close() or die(htmlspecialchars($stmt->error));
                // coookie expiry
                if ((time() - $ctime) > 60 * 60 * 24 * COOKIE_TIME_OUT) {
                    $this->signout();
                }
                /* Security check with untrusted cookies - dont trust value stored in cookie.       
                  /* We also do authentication check of the `ckey` stored in cookie matches that stored in database during login */

                if (!empty($ckey) && is_numeric($_COOKIE['user_id']) && $_COOKIE['key'] == sha1($ckey)) {
                    session_regenerate_id(); //against session fixation attacks.

                    $_SESSION['user_id'] = $_COOKIE['user_id'];
                    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
                } else {
                    $this->signout();
                }
            } else {
                if ($page != 'main') {
                    header('Location:' . wsurl);
                    exit();
                }
            }
        }

答案 1

设置登录尝试之间的时间延迟(以防止暴力攻击)和限制尝试次数

所以你是按帐户为DOS提供一种方法吗?

在几乎每个操作上重新生成会话 ID

嗯,没有。这实际上很可能会打败对象。当当前 ID 过期或用户通过身份验证时,应始终生成一个新 ID,否则不要理会它。

但我真的对我的主要问题感到困惑:对于“记住我”功能,哪种方式是正确的?使用饼干/会话/数据库?

由于您需要在客户端上保留令牌,这意味着cookie(除非您喜欢使用本地存储编写非常复杂的东西)。由于您不想通过cookie公开数据/使伪造变得简单,这意味着它应该是一个随机值。为了协调存储的随机值,这意味着在服务器端存储数据 - 可能在数据库中,因为必须能够基于用户ID或基于随机值引用数据。

虽然你可以只使用一个未过期(或非常长寿命)的会话,但我会远离这个 - 数据会滚雪球 - 并且偶尔更新会话数据是个好主意。

您还需要满足用户希望您在 2 台不同计算机上记住她的方案。如果您只为每个帐户持有一个“记住我”令牌,那么您要么必须在两个客户端上使用相同的值,要么在创建新令牌时删除旧令牌(即用户只能在一台计算机上记住)。

请解释你对代码的想法。没有代码,我无法清楚地理解

不,我得到报酬来编写代码;如果你想让我为你写代码,那么你需要付钱给我。代码将比上面的描述占用更多的空间和时间。


答案 2

如果你想要一个带有代码的“记住我”函数的例子,你可以在 https://github.com/gbirke/rememberme


推荐