php PHP登录系统:Remember Me(持久cookie)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3128985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PHP login system: Remember Me (persistent cookie)
提问by Karem
I'd like to add a "remember me" checkbox option before logging in.
我想在登录前添加一个“记住我”复选框选项。
What is the best way to securely store a cookie in the user's browser?
在用户浏览器中安全存储 cookie 的最佳方法是什么?
For example, Facebook have their "remember me" checkbox so that every time you enter facebook.com you are already logged in.
例如,Facebook 有他们的“记住我”复选框,以便您每次进入 facebook.com 时都已经登录。
My current login uses simple sessions.
我当前的登录使用简单的会话。
回答by Scott Arciszewski
Update (2017-08-13): To understand why we're separating
selectorandtoken, instead of just using atoken, please read this article about splitting tokens to prevent timing attackson SELECT queries.
更新 (2017-08-13):要了解我们为什么要分离
selector和token,而不是仅仅使用token,请阅读这篇关于拆分令牌以防止对 SELECT 查询进行计时攻击的文章。
I'm going to extract the strategy outlined in this blog post about secure long-term authenticationsince that covers a lot of ground and we're only interested in the "remember me"part.
我将提取这篇博文中概述的关于安全长期身份验证的策略,因为它涵盖了很多领域,我们只对“记住我”部分感兴趣。
Preamble - Database Structure
序言 - 数据库结构
We want a separate table from our users' table that looks like this (MySQL):
我们想要一个与用户表不同的表,看起来像这样(MySQL):
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`)
);
The important things here are that selectorand tokenare separate fields.
这里重要的是,selector和token是单独的字段。
After Logging In
登录后
If you don't have random_bytes(), just grab a copy of random_compat.
如果没有random_bytes(),只需获取random_compat的副本。
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)
]
);
}
Re-Authenticating On Page Load
在页面加载时重新验证
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
}
}
Details
细节
We use 9 bytes of random data (base64 encoded to 12 characters) for our selector. This provides 72 bits of keyspace and therefore 236bits of collision resistance (birthday attacks), which is larger than our storage capacity (integer(11) UNSIGNED) by a factor of 16.
我们使用 9 字节的随机数据(base64 编码为 12 个字符)作为我们的选择器。这提供了 72 位的密钥空间,因此提供了 2 36位的抗碰撞性(生日攻击),这比我们的存储容量 ( integer(11) UNSIGNED) 大了 16 倍。
We use 33 bytes (264 bits) of randomness for our actual authenticator. This should be unpredictable in all practical scenarios.
我们将 33 字节(264 位)的随机性用于我们的实际身份验证器。这在所有实际场景中都应该是不可预测的。
We store an SHA256 hash of the authenticator in the database. This mitigates the risk of user impersonation following information leaks.
我们将身份验证器的 SHA256 哈希值存储在数据库中。这降低了信息泄露后用户冒充的风险。
We re-calculate the SHA256 hash of the authenticator value stored in the user's cookie then compare it with the stored SHA256 hash using hash_equals()to prevent timing attacks.
我们重新计算存储在用户 cookie 中的验证器值的 SHA256 哈希值,然后将其与存储的 SHA256 哈希值进行比较,hash_equals()以防止定时攻击。
We separated the selector from the authenticator because DB lookups are not constant-time. This eliminates the potential impact of timing leaks on searches without causing a drastic performance hit.
我们将选择器与验证器分开,因为数据库查找不是恒定时间的。这消除了时间泄漏对搜索的潜在影响,而不会造成严重的性能损失。

