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

时间:2012-01-18 08:47:32

标签: php session cookies login remember-me

使用登录系统并尝试实现记住我的功能。

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

并决定实现以下目标

  1. 设置登录尝试之间的时间延迟(以防止暴力攻击)和限制尝试次数
  2. 几乎每次操作都要重新生成会话ID
  3. 我真的很困惑我的主要问题:哪种方式适合“记住我”功能?使用cookies / session / database?

    请在代码上解释你的想法。(没有代码,我无法理解)

    详细

    目前,我的代码看起来像

    在登录期间,我使用以下功能设置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以从db获取有关用户的所有重要数据

    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();
                    }
                }
            }
    

4 个答案:

答案 0 :(得分:8)

  

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

所以你是按帐户提供DOS的方法吗?

  

几乎每次操作都重新生成会话ID

呃,没有。这实际上可能会击败对象。当前id过期或用户通过身份验证时,您应始终生成新的ID - 否则请不要管它。

  

但我对我的主要问题感到困惑:对于“记住我”这个功能,哪种方式适当?使用cookies / session / database?

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

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

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

  

请解释您对代码的想法。没有代码我无法理解

没有。我得到报酬写代码;如果您希望我为您编写代码,那么您需要付我钱。代码将比上面的描述占用更多的空间和时间。

答案 1 :(得分:3)

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

查看我的PHP库。

答案 2 :(得分:2)

  

但我对我的主要问题感到困惑:对于“记住我”这个功能,哪种方式适当?使用cookies / session / database?

Http是一个无国籍的protocall。身份验证令牌必须持久保持状态。 正确的方法是使用会话。现在你如何跟踪会话?由你决定。但饼干也不错。

在会话中,您可以保存根据浏览器不同条件(用户代理,操作系统,屏幕分辨率等)创建的哈希,以检查令牌是否来自相同的环境。你保存的标准越多,劫持就越难。顺便说一句,你每次都需要JavaScript来获取额外的信息。

答案 3 :(得分:-1)

Cookie是最佳解决方案。请查看以下代码:

<form action="" method="post" id="frmLogin">
<div class="error-message"><?php if(isset($message)) { echo $message; } ?></div>    
<div class="field-group">
    <div><label for="login">Username</label></div>
    <div><input name="member_name" type="text" value="<?php if(isset($_COOKIE["member_login"])) { echo $_COOKIE["member_login"]; } ?>" class="input-field">
</div>
<div class="field-group">
    <div><label for="password">Password</label></div>
    <div><input name="member_password" type="password" value="<?php if(isset($_COOKIE["member_password"])) { echo $_COOKIE["member_password"]; } ?>" class="input-field"> 
</div>
<div class="field-group">
    <div><input type="checkbox" name="remember" id="remember" <?php if(isset($_COOKIE["member_login"])) { ?> checked <?php } ?> />
    <label for="remember-me">Remember me</label>
</div>
<div class="field-group">
    <div><input type="submit" name="login" value="Login" class="form-submit-button"></span></div>
</div>       

session_start();
if(!empty($_POST["login"])) {
    $conn = mysqli_connect("localhost", "root", "", "blog_samples");
    $sql = "Select * from members where member_name = '" . $_POST["member_name"] . "' and member_password = '" . md5($_POST["member_password"]) . "'";
    $result = mysqli_query($conn,$sql);
    $user = mysqli_fetch_array($result);
    if($user) {
            $_SESSION["member_id"]         = $user["member_id"];

            if(!empty($_POST["remember"])) {
                setcookie ("member_login",$_POST["member_name"],time()+ (10 * 365 * 24 * 60 * 60));
                setcookie ("member_password",$_POST["member_password"],time()+ (10 * 365 * 24 * 60 * 60));
            } else {
                if(isset($_COOKIE["member_login"])) {
                    setcookie ("member_login","");
                }
                if(isset($_COOKIE["member_password"])) {
                    setcookie ("member_password","");
                }
            }
    } else {
        $message = "Invalid Login";
    }
}