我目前使用会话将用户登录到我的网站。现在,我想在混合中添加cookie,以便人们可以在几天后访问该网站,而无需再次登录。
我不想将密码存储在cookie中或加密任何东西 - 所以我的解决方案是将用户名存储在cookie中,并使用一种登录哈希创建另一个cookie(特定于每个用户的每次登录)。然后,在每个页面的顶部,检查cookie和登录会话。如果cookie存在但会话不存在,请找到用户名并将用户登录。
因此,这会在他们成功登录后发生($row['username']
是他们输入的用户名):
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 32);
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;
$today=date("Y-m-d");
mysql_query("update members set last_login='$today',loginhash='$loginhash' where id='$row[id]' ") or die(mysql_error());
然后在每个页面的顶部,执行以下操作:
if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
$username=mysql_real_escape_string($_COOKIE['userlogin']);
$loginhash=mysql_real_escape_string($_COOKIE['loginhash']);
$result=mysql_query("select * from members where (username='$username' || email='$username') && loginhash='$loginhash' ") or die (mysql_error());
$row=mysql_fetch_array($result);
$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
}
然后,当然,当用户手动注销时取消设置cookie。
我的问题是,这是否安全(除了其他人使用用户计算机的危险)?每次登录都会生成随机哈希,因此有人不能仅使用某人的用户名创建cookie并以该人身份登录。
登录脚本使用HTTPS,因此该方面没有危险。
答案 0 :(得分:6)
我可以看到你这样做的两个主要问题:
我们不在数据库中存储密码的原因是,如果数据库受到攻击,攻击者无法访问我们服务器上的帐户......但这正是你的意思正在处理你的loginhash
。 如果我破坏了您的数据库,我可以通过创建一个包含username
和loginhash
的Cookie的方式,以任何具有活动Cookie的用户在接下来的14天内登录数据库中。
您可以通过生成随机密钥,将其存储在cookie中,然后通过像bcrypt
这样的散列函数运行并将摘要存储在数据库中来解决此问题。这样,即使完全转储数据库,用户也不能像其他任何用户一样简单地登录您的站点。当然,您必须更改比较运算符,以便比较摘要而不是原始数据。
str_shuffle
函数在加密方面不安全。它在内部使用rand
这是相当可预测的,并且会使这个哈希比你的密码更容易攻击向量。相反,在你的操作系统上使用随机熵(URANDOM指的是Unblocking Random,它不像RANDOM那样安全,但是你的系统会进行大量的处理 - 从而产生熵 - 这就足够了),如下所示:
function generateRandomBase64String($length = 32)
{
if (!defined('MCRYPT_DEV_URANDOM')) die('The MCRYPT_DEV_URANDOM source is required (PHP 5.3).');
$binaryLength = (int)($length * 3 / 4 + 1);
$randomBinaryString = mcrypt_create_iv($binaryLength, MCRYPT_DEV_URANDOM);
$randomBase64String = base64_encode($randomBinaryString);
return substr($randomBase64String, 0, $length);
}
This article解释了Drupal如何处理登录令牌,每次用户使用凭据登录而不是使用Cookie时,都会将username
,loginhash
和series_id
存储在一起。可以使用上述随机函数生成loginhash
和series_id
。
当用户登录时,将检查数据库。如果username
,loginhash
和series_id
都存在,则用户已登录,并且生成的新Cookie 具有相同的username
和series_id
但是新的loginhash
。这意味着每个cookie只能使用一次。
此外,以这种方式登录的用户无法执行任何妥协功能,例如请求密码重置,取款和查看敏感信息而无需“正确”密码登录。这是为了保护已经拥有cookie的用户。
如果检测到series_id
存在且loginhash
不匹配的登录信息,我们会擦除此用户的所有有效Cookie,并留下一条措辞强烈的消息,指出使用了无效的Cookie访问他的帐户。
mysql_*
函数这似乎是新代码,尚未部署。如果还不太晚,请考虑将数据库驱动程序切换到mysqli
(documentation),它保持与mysql
非常相似的程序样式,但允许您使用绑定参数和其他现代提高数据安全性的功能。
答案 1 :(得分:1)
您应该改进一些要点:
首先,使用操作系统随机源(URANDOM)最好生成登录令牌。您的代码将仅使用每个字符一次,并且shuffle函数可能基于当前时间(随机数)。因此,了解大致的登录时间将严重缩小可能的组合范围。
令牌不应该以明文形式存储在数据库中,而应该只在数据库中存储它的哈希值。如果有人可以读取您的数据库(SQL注入),他仍然无法使用该代码。
函数setcookie()
有两个参数$secure
和$httponly
,您应将两者都设置为true。否则,可以欺骗浏览器将未加密的令牌发送到HTTP页面(它甚至会被发送到图像请求)。