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

Posted

技术标签:

【中文标题】在 PHP 中使用“记住我”功能的正确方法【英文标题】:Proper way to use "Remember me" functionality in PHP 【发布时间】:2012-02-13 00:12:09 【问题描述】:

正在开发登录系统并尝试实现记住我的功能。

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

并决定实现以下目标

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

但是我真的很困惑我的主要问题:哪种方式适合“记住我”功能?使用 cookie/会话/数据库?

请解释一下你对代码的想法。(没有代码我看不懂)

详细

目前,我的代码是这样的

在登录期间,我使用以下函数来设置 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();
                
            
        

【问题讨论】:

只是评论:从用户体验的角度来看,我认为让您的用户知道您的意思是“让我保持登录”还是只是“记住我的身份”是一个好点。 一个可能的副本:***.com/questions/244882/… @ThinkingMonkey 好吧,你很酷:) 生成唯一的、非常长的随机令牌在服务器上,将它们存储在您的数据库中以供以后查找和在会话中,它通常会在后台将一些会话标识符写入 cookie。或者使用像 github.com/delight-im/php-Auth 这样的库来为你做这一切。 【参考方案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";
    

【讨论】:

欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.【参考方案2】:

但我真的很困惑我的主要问题:哪种方式适合“记住我”功能?使用 cookie/会话/数据库?

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

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

【讨论】:

我讨厌投反对票,但try sending it over URL(I know it looks ugly). - 确实是投反对票的目标。这是众所周知的 cookie 固定攻击 漏洞【参考方案3】:

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

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

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

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

但我真的很困惑我的主要问题:哪种方式适合“记住我”功能?使用 cookie/会话/数据库?

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

虽然您可以只使用不会过期(或寿命很长)的会话,但我会远离这种情况 - 数据会滚雪球 - 不时更新会话数据是个好主意。

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

请解释您对代码的想法。没有代码我看不懂

没有。我通过编写代码获得报酬;如果你想让我为你写代码,那么你需要付钱给我。而且代码会比上面的描述占用更多的空间和时间。

【讨论】:

在这个社区中,我们正在努力互相帮助。我也是开发者。只是想获得建议和您想法的一些代码示例,(我不会说英语。并非您的所有帖子对我来说都是 100% 清楚的)无论如何,感谢您免费浪费您的时间:) SO 上 90% 的用户通过编写代码获得报酬。然而,只有一小部分人持有你的态度。【参考方案4】:

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

【讨论】:

我让它在 Firefox 上运行。但是在 chrome 上,您的测试文件不起作用:停留在第一页,无法登录。 我可以占用您 5-10 分钟的时间吗?请在 Skype 中添加我 tural.teyyuboglu

以上是关于在 PHP 中使用“记住我”功能的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

我在我的页面的 php 登录中添加一个简单的记住我功能时遇到问题?

如何安全实现“记住我”功能

记住我功能的最佳实践[重复]

PHP记住cookie中的登录状态[重复]

关于【记住我的凭据】,老是记不住!!

ExtJS:使用“记住我”功能登录