关于身份验证:PHP登录系统:记住我(持久性cookie)

PHP login system: Remember Me (persistent cookie)

本问题已经有最佳答案,请猛点这里访问。

我想在登录前添加一个"记住我"复选框选项。

在用户浏览器中安全存储cookie的最佳方法是什么?

例如,Facebook有他们的"记住我"复选框,这样每当你进入facebook.com,你就已经登录了。

我当前的登录使用简单会话。


Update (2017-08-13): To understand why we're separating selector and token, instead of just using a token, please read this article about splitting tokens to prevent timing attacks on SELECT queries.

我将提取这篇关于安全长期认证的博客文章中概述的策略,因为这涉及很多领域,我们只对"记住我"部分感兴趣。

前言-数据库结构

我们需要一个与用户表分开的表,如下所示(mysql):

1
2
3
4
5
6
7
8
CREATE TABLE `auth_tokens` (
    `id` integer(11) not null UNSIGNED AUTO_INCREMENT,
    `selector` char(12),
    `token` char(64),
    `userid` integer(11) not null UNSIGNED,
    `expires` datetime,
    PRIMARY KEY (`id`)
);

这里重要的是,selectortoken是单独的字段。

登录后

如果你没有random_bytes(),只需要随机获取一份兼容文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if ($login->success && $login->rememberMe) { // However you implement it
    $selector = base64_encode(random_bytes(9));
    $authenticator = random_bytes(33);

    setcookie(
        'remember',
         $selector.':'.base64_encode($authenticator),
         time() + 864000,
         '/',
         'yourdomain.com',
         true, // TLS-only
         true  // http-only
    );

    $database->exec(
       "INSERT INTO auth_tokens (selector, token, userid, expires) VALUES (?, ?, ?, ?)",
        [
            $selector,
            hash('sha256', $authenticator),
            $login->userId,
            date('Y-m-d\TH:i:s', time() + 864000)
        ]
    );
}

号页面加载时重新验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (empty($_SESSION['userid']) && !empty($_COOKIE['remember'])) {
    list($selector, $authenticator) = explode(':', $_COOKIE['remember']);

    $row = $database->selectRow(
       "SELECT * FROM auth_tokens WHERE selector = ?",
        [
            $selector
        ]
    );

    if (hash_equals($row['token'], hash('sha256', base64_decode($authenticator)))) {
        $_SESSION['userid'] = $row['userid'];
        // Then regenerate login token as above
    }
}

细节

我们使用9字节的随机数据(base64编码为12个字符)作为选择器。这提供了72位的密钥空间,因此236位的抗碰撞(生日攻击),比我们的存储容量(integer(11) UNSIGNED大16倍。

我们使用33字节(264位)的随机性作为实际验证器。在所有实际场景中,这都是不可预测的。

我们将authenticator的sha256散列存储在数据库中。这可以降低信息泄漏后用户冒充的风险。

我们重新计算存储在用户cookie中的authenticator值的sha256哈希,然后使用hash_equals()将其与存储的sha256哈希进行比较,以防止定时攻击。

我们将选择器与authenticator分离,因为db查找不是恒定时间。这消除了定时泄漏对搜索的潜在影响,而不会造成严重的性能损失。


这个问题经常被问到,这里有一些链接给你。

  • 实施安全"记住我"的最佳实践
  • "在这台电脑上记住我"-它应该如何工作?
  • "让我登录"-最好的方法

在回答这个问题时,我们还收集了一些很好的资源:网站认证的最终指南