“让我登录” – 最好的方法

我的Web应用程序使用会话在用户登录后存储用户的相关信息,并在应用程序中的页面之间传递信息时维护这些信息。 在这个特定的应用程序中,我存储了该用户的user_idfirst_namelast_name

我想在登录时提供一个“Keep Me登录”选项,这个选项会在用户的机器上放置一个cookie两周,当他们返回到应用程序时,会重新启动他们的会话。

这样做的最好方法是什么? 我不想将它们的user_id存储在cookie中,因为它似乎会使一个用户很容易尝试伪造另一个用户的身份。

安全公告 :将cookie放在确定性数据的MD5哈希上是一个坏主意; 最好使用从CSPRNG派生的随机令牌。 请参阅ircmaxell对此问题的回答 ,以获得更安全的方法。

通常我会这样做:

  1. 用户使用“保持登录状态”登录
  2. 创建会话
  3. 创建一个名为SOMETHING的cookie,包含:md5(salt + username + ip + salt)和一个名为somethingElse的cookie,其中包含id
  4. 将cookie存储在数据库中
  5. 用户做东西和叶子—-
  6. 用户返回,检查是否存在cookie,如果存在,从该用户的数据库中获取旧的散列,检查cookie SOMETHING的内容是否匹配来自数据库的散列,这也应该与新计算的散列相匹配ip):cookieHash == databaseHash == md5(salt + username + ip + salt),如果他们这样做,转到2,如果他们不转到1

当然,你可以使用不同的cookie名称等,也可以改变cookie的内容,只是确保它不容易创建。 例如,你也可以在创建用户的时候创建一个user_salt,并把它放到cookie中。

也可以使用sha1而不是md5(或几乎任何算法)

好的,让我直言不讳地说:如果您将用户数据或从用户数据派生的任何内容放入Cookie中,您就是在做错事。

那里。 我说了。 现在我们可以转到实际的答案。

散列用户数据有什么问题,你问? 那么,这归结于暴露的表面和安全通过晦涩。

想象一下,你是一个攻击者。 您在会话中看到为记事本设置的加密cookie。 这是32个字符宽。 啧啧。 这可能是一个MD5 …

让我们也想象一下,他们知道你使用的算法。 例如:

 md5(salt+username+ip+salt) 

现在,攻击者所需要做的就是对“盐”进行蛮力操作(其实不是盐,而是稍后再做),现在他可以用任何用户名为他的IP地址生成所有他想要的假令牌! 但是蛮咸的是很难的,对吧​​? 绝对。 但现代的GPU非常擅长。 除非你使用足够的随机性(足够大),它会很快下降,并与它的钥匙到你的城堡。

总之,保护你的唯一的东西就是盐,它不像你想的那样保护你。

可是等等!

所有这一切都预示着攻击者知道算法! 如果它是秘密和混乱的,那么你是安全的,对吧? 错了 。 这一思路有一个名字: 安全通过默默无闻 ,应该永远不能依靠。

更好的方式

更好的方法是不要让用户的信息离开服务器,除了id。

当用户登录时,生成一个大的(128到256位)随机令牌。 将其添加到将令牌映射到用户标识的数据库表中,然后将其发送到客户端的Cookie中。

如果攻击者猜测另一个用户的随机标记呢?

那么,我们在这里做一些数学。 我们正在生成一个128位随机令牌。 这意味着有:

 possibilities = 2^128 possibilities = 3.4 * 10^38 

现在,为了说明这个数字有多荒谬,让我们想象一下互联网上的每台服务器(假设今天有5000万台服务器)试图以每秒10亿次的速度暴力破解这个数字。 实际上你的服务器会在这样的负载下融化,但是我们来玩吧。

 guesses_per_second = servers * guesses guesses_per_second = 50,000,000 * 1,000,000,000 guesses_per_second = 50,000,000,000,000,000 

所以每秒钟有50万亿次的猜测。 这很快! 对?

 time_to_guess = possibilities / guesses_per_second time_to_guess = 3.4e38 / 50,000,000,000,000,000 time_to_guess = 6,800,000,000,000,000,000,000 

所以6.8 sextillion秒…

让我们试着把它们归结为更友好的数字。

 215,626,585,489,599 years 

甚至更好:

 47917 times the age of the universe 

是的,这是宇宙年龄的47917倍

基本上,它不会被破解。

所以总结一下:

我推荐的更好的方法是用三部分来存储cookie。

 function onLogin($user) { $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit storeTokenForUser($user, $token); $cookie = $user . ':' . $token; $mac = hash_hmac('sha256', $cookie, SECRET_KEY); $cookie .= ':' . $mac; setcookie('rememberme', $cookie); } 

然后,验证:

 function rememberMe() { $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : ''; if ($cookie) { list ($user, $token, $mac) = explode(':', $cookie); if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) { return false; } $usertoken = fetchTokenByUserName($user); if (hash_equals($usertoken, $token)) { logUserIn($user); } } } 

注意:不要使用令牌或用户和令牌的组合来查找数据库中的记录。 总是要确保根据用户获取记录,然后使用时间安全的比较函数比较获取的令牌。 更多关于定时攻击 。

现在, SECRET_KEY是一个密码秘密(由/dev/urandom和/或从高熵输入产生)是非常重要的。 此外, GenerateRandomToken()需要是一个强大的随机源( mt_rand()不够强大。使用一个库,如RandomLib或random_compat或mcrypt_create_iv()DEV_URANDOM )…

hash_equals()是为了防止定时攻击 。 如果您使用低于PHP 5.6的PHP版本,则不支持函数hash_equals() 。 在这种情况下,您可以用timingSafeCompare函数替换hash_equals()

 /** * A timing safe equals comparison * * To prevent leaking length information, it is important * that user input is always used as the second parameter. * * @param string $safe The internal (safe) value to be checked * @param string $user The user submitted (unsafe) value * * @return boolean True if the two strings are identical. */ function timingSafeCompare($safe, $user) { if (function_exists('hash_equals')) { return hash_equals($safe, $user); // PHP 5.6 } // Prevent issues if string length is 0 $safe .= chr(0); $user .= chr(0); // mbstring.func_overload can make strlen() return invalid numbers // when operating on raw binary strings; force an 8bit charset here: if (function_exists('mb_strlen')) { $safeLen = mb_strlen($safe, '8bit'); $userLen = mb_strlen($user, '8bit'); } else { $safeLen = strlen($safe); $userLen = strlen($user); } // Set the result to the difference between the lengths $result = $safeLen - $userLen; // Note that we ALWAYS iterate over the user-supplied length // This is to prevent leaking length information for ($i = 0; $i < $userLen; $i++) { // Using % here is a trick to prevent notices // It's safe, since if the lengths are different // $result is already non-0 $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i])); } // They are only identical strings if $result is exactly 0... return $result === 0; } 

介绍

你的头衔“让我登录” – 最好的方法使我很难知道从哪里开始,因为如果你正在寻找最好的方法,那么你将不得不考虑以下几点:

  • 鉴定
  • 安全

饼干

Cookie是脆弱的,在常见的浏览器cookie-盗窃漏洞和跨站点脚本攻击之间,我们必须接受cookie不安全。 为了帮助提高安全性,你必须注意到, php setcookies具有其他功能,如

bool setcookie (string $ name [,string $ value [,int $ expire = 0 [,string $ path [,string $ domain [,bool $ secure = false [,bool $ httponly = false]]]]]])

  • 安全(使用HTTPS连接)
  • httponly(通过XSS攻击减少身份盗用)

定义

  • 令牌(n个不可预知的随机字符串,例如/ dev / urandom)
  • 参考(n个长度的不可预知的随机字符串,例如/ dev / urandom)
  • 签名(使用HMAC方法生成密钥哈希值)

简单的方法

一个简单的解决方案是:

  • 用户使用“记住我”登录
  • 用令牌&签名发布登录Cookie
  • 什么时候回来,签名被检查
  • 如果签名是好的,那么在数据库中查找用户名和令牌
  • 如果无效..返回到登录页面
  • 如果有效自动登录

上面的案例研究总结了本页给出的所有例子,但是它们的缺点在于

  • 没有办法知道cookies是否被盗
  • 攻击者可能是访问敏感的操作,如更改密码或数据,如个人信息和烘焙信息等。
  • 受损的cookie对于cookie的使用期限仍然有效

更好的方案

更好的解决方案是

  • 用户已登录并记住我已选中
  • 生成令牌和签名并存储在cookie中
  • 令牌是随机的,只对单一认证有效
  • 令牌在每次访问网站时都会被替换
  • 当没有登录的用户访问该网站时,签名,令牌和用户名都被验证
  • 记住我登录应该有有限的访问权限,不允许修改密码,个人信息等。

示例代码

 // Set privateKey // This should be saved securely $key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282'; $key = pack("H*", $key); // They key is used in binary form // Am Using Memecahe as Sample Database $db = new Memcache(); $db->addserver("127.0.0.1"); try { // Start Remember Me $rememberMe = new RememberMe($key); $rememberMe->setDB($db); // set example database // Check if remember me is present if ($data = $rememberMe->auth()) { printf("Returning User %s\n", $data['user']); // Limit Acces Level // Disable Change of password and private information etc } else { // Sample user $user = "baba"; // Do normal login $rememberMe->remember($user); printf("New Account %s\n", $user); } } catch (Exception $e) { printf("#Error %s\n", $e->getMessage()); } 

使用的类

 class RememberMe { private $key = null; private $db; function __construct($privatekey) { $this->key = $privatekey; } public function setDB($db) { $this->db = $db; } public function auth() { // Check if remeber me cookie is present if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) { return false; } // Decode cookie value if (! $cookie = @json_decode($_COOKIE["auto"], true)) { return false; } // Check all parameters if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) { return false; } $var = $cookie['user'] . $cookie['token']; // Check Signature if (! $this->verify($var, $cookie['signature'])) { throw new Exception("Cokies has been tampared with"); } // Check Database $info = $this->db->get($cookie['user']); if (! $info) { return false; // User must have deleted accout } // Check User Data if (! $info = json_decode($info, true)) { throw new Exception("User Data corrupted"); } // Verify Token if ($info['token'] !== $cookie['token']) { throw new Exception("System Hijacked or User use another browser"); } /** * Important * To make sure the cookie is always change * reset the Token information */ $this->remember($info['user']); return $info; } public function remember($user) { $cookie = [ "user" => $user, "token" => $this->getRand(64), "signature" => null ]; $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']); $encoded = json_encode($cookie); // Add User to database $this->db->set($user, $encoded); /** * Set Cookies * In production enviroment Use * setcookie("auto", $encoded, time() + $expiration, "/~root/", * "example.com", 1, 1); */ setcookie("auto", $encoded); // Sample } public function verify($data, $hash) { $rand = substr($hash, 0, 4); return $this->hash($data, $rand) === $hash; } private function hash($value, $rand = null) { $rand = $rand === null ? $this->getRand(4) : $rand; return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true)); } private function getRand($length) { switch (true) { case function_exists("mcrypt_create_iv") : $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); break; case function_exists("openssl_random_pseudo_bytes") : $r = openssl_random_pseudo_bytes($length); break; case is_readable('/dev/urandom') : // deceze $r = file_get_contents('/dev/urandom', false, null, 0, $length); break; default : $i = 0; $r = ""; while($i ++ < $length) { $r .= chr(mt_rand(0, 255)); } break; } return substr(bin2hex($r), 0, $length); } } 

在Firefox和Chrome中测试

在这里输入图像描述

优点

  • 更好的安全性
  • 攻击者访问受限
  • 当cookie被盗时,它仅对单一访问有效
  • 当下一个原始用户访问该网站时,您可以自动检测并通知用户盗窃

坏处

  • 不支持通过多个浏览器进行持续连接(Mobile&Web)
  • 该cookie仍然可以被盗用,因为用户在下次登录后才会收到通知。

快速解决

  • 为每个必须具有持续连接的系统引入审批系统
  • 使用多个cookie进行身份验证

多重Cookie方法

当一个攻击者即将窃取烹饪的唯一焦点在一个特定的网站或域名,例如。 example.com

但是,真的,你可以从2个不同的域( example.comfakeaddsite.com )认证一个用户,并使其看起来像“广告Cookie”

  • 用户登录到example.com与记住我
  • 在cookie中存储用户名,标记,引用
  • 在数据库中存储用户名,标记,引用。 内存缓存
  • 通过get和iframe将refrence id发送到fakeaddsite.com
  • fakeaddsite.com使用引用从数据库中获取用户和令牌
  • fakeaddsite.com存储签名
  • 当用户正在从fakeaddsite.com返回iframe的签名信息时
  • 结合数据并进行验证
  • …..你知道剩下的

有些人可能想知道如何使用2个不同的饼干? 那么可能呢,想象一下example.com = localhostfakeaddsite.com = 192.168.1.120 。 如果你检查饼干​​看起来像这样

在这里输入图像描述

从上面的图像

  • 当前访问的网站是localhost
  • 它还包含从192.168.1.120设置的Cookie

192.168.1.120

  • 只接受定义的HTTP_REFERER
  • 只接受来自指定REMOTE_ADDR连接
  • 没有JavaScript,没有内容,但没有任何东西,而不是签名信息,并从cookie中添加或检索它

优点

  • 99%的时间欺骗了攻击者
  • 您可以轻松锁定攻击者首次尝试的帐户
  • 即使在下次登录之前,也可以像其他方法一样防止攻击

坏处

  • 多个请求到服务器只为一个登录

起色

  • 完成使用iframe使用ajax

有两篇非常有趣的文章,我在寻找“记住我”问题的完美解决方案时发现:

  • 持久登录Cookie最佳实践
  • 改进的持久登录Cookie最佳实践

我在这里问了一个问题的一个角度,答案会引导你到所有你需要的基于令牌的超时cookie链接。

基本上,你不存储在cookie中的userId。 您存储用户用来提取旧登录会话的一次性令牌(巨大字符串)。 然后为了确保安全,你需要一个密码来执行繁重的操作(比如改变密码本身)。

我会推荐Stefan提到的方法(即按照改进的持久登录Cookie最佳实践中的指导原则),并建议您确保您的Cookie是HttpOnly Cookie,以便他们无法访问恶意JavaScript。

生成一个哈希,也许只有你知道的秘密,然后将其存储在您的数据库,以便它可以与用户关联。 应该工作得很好。

老线程,但仍然是一个有效的关注。 我注意到了一些关于安全的好的回应,并且避免使用“默默无闻的安全性”,但是我所看到的实际技术方法还不够。 在我贡献我的方法之前我必须说的事情:

  • 切勿以明文存储密码…永远!
  • 切勿将用户的散列密码存储在数据库中的多个位置。 您的服务器后端始终能够从用户表中提取散列的密码。 存储冗余数据来代替额外的数据库事务并不是更有效率,反之亦然。
  • 你的会话ID应该是唯一的,所以没有两个用户可以共享一个ID,因此一个ID的目的(你的驾驶执照ID号可以匹配另一个人吗?否)。这产生了一个两件式独特的组合,基于2独特的字符串 您的会话表应该使用ID作为PK。 要允许多个设备被信任进行自动登录,请使用另一个包含所有验证设备列表的可信设备表(请参阅下面的示例),并使用用户名映射。
  • 它没有任何目的将已知数据散列到cookie中,可以复制cookie。 我们正在寻找的是一个合规的用户设备,以提供真正的信息,而攻击者无法攻破用户的机器(见我的例子)。 然而,这意味着合法用户禁止他的机器的静态信息(即MAC地址,设备主机名,用户权限,如果受到浏览器的限制等)保持一致(或者首先欺骗它)将不能使用此功能。 但是,如果这是一个问题,考虑一下这个事实,即您提供自动登录的用户身份唯一标识 ,所以如果他们拒绝通过欺骗他们的MAC,欺骗他们的用户,欺骗/改变他们的主机名,隐藏在代理人后面,那么它们是不可识别的,并且不应该被认证用于自动服务。 如果您需要,您需要查看与客户端软件捆绑在一起的智能卡访问,这些软件为所使用的设备建立身份。

所有人都说,在你的系统上有两种很好的自动登录方式。

首先,便宜,简单的方法,把所有的一切都放在别人身上。 如果您的网站支持使用google +帐户进行登录,那么您可能已经有了一个精简的Google +按钮,如果用户已经登录到Google,就可以登录该用户(我在这里是为了回答这个问题,因为我总是登录到谷歌)。 如果您希望用户自动登录(如果他们已经使用受信任和受支持的身份验证程序登录),并选中此框,请在加载之前让您的客户端脚本在相应的“使用登录”按钮后面执行代码,请确保服务器在自动登录表中存储一个唯一的ID,该表具有用户名,会话ID和用户使用的身份验证器。 由于这些登录方法使用AJAX,因此无论如何您都在等待响应,并且响应是经过验证的响应或拒绝。 如果获得验证的响应,请照常使用,然后继续加载已登录的用户。 否则,登录失败,但不告诉用户,只要继续未登录,他们会注意到。 这是为了防止攻击者窃取cookie(或伪造他们试图提升权限),从而获知用户自动登录到站点。

这很便宜,也可能被一些人认为是肮脏的,因为它试图验证你可能已经登录自己与谷歌和Facebook的地方,甚至没有告诉你。 但是,它不应该用于没有要求自动登录您的网站的用户,而这种特定的方法只适用于外部身份验证,如使用Google+或FB。

由于使用外部身份验证程序在幕后告诉服务器是否验证了用户,因此攻击者无法获取除了唯一ID以外的任何内容,而这些ID本身是无用的。 我将详细说明:

  • 用户'乔'第一次访问网站,会话ID放在cookie'会话'。
  • 用户'joe'登录,升级权限,获取新的会话ID并更新cookie'会话'。
  • 用户“乔”选择使用谷歌+自动登录,获取一个唯一的ID放在cookie“keepmesignedin”。
  • 用户'乔'谷歌让他们登录,让您的网站自动登录用户在您的后端使用谷歌。
  • 攻击者系统地为“keepmesignin”(这是向每个用户发布的公共知识)尝试唯一的ID,并且不在其他任何地方签名; 尝试赋予“乔”的唯一ID。
  • 服务器收到“joe”的唯一ID,并在数据库中为Google +帐户提取匹配。
  • 服务器发送攻击者登录页面运行一个AJAX请求谷歌登录。
  • Google服务器接收到请求,使用其API查看攻击者当前未登录。
  • Google会通过此连接发送当前没有用户登录的响应。
  • 攻击者的页面接收到响应,脚本自动重定向到登录页面,并使用在URL中编码的POST值。
  • 登录页面获取POST值,将“keepmesignedin”的cookie发送到一个空值,并且有效期为1-1-1970,以阻止自动尝试,导致攻击者的浏览器简单地删除cookie。
  • 攻击者被给予正常的首次登录页面。

无论如何,即使攻击者使用不存在的ID,除非收到验证的响应,否则所有尝试都将失败。

对于那些使用外部身份验证程序登录您的网站的用户,此方法可以并且应该与您的内部身份验证程序一起使用。

=========

现在,为了您自己的身份验证系统,可以自动登录用户,这是我如何做到这一点:

DB有几个表格:

 TABLE users: UID - auto increment, PK username - varchar(255), unique, indexed, NOT NULL password_hash - varchar(255), NOT NULL ... 

请注意,用户名长度可以达到255个字符。 我的服务器程序在我的系统中将用户名限制为32个字符,但外部认证者可能拥有@ domain.tld的用户名,因此我只是支持电子邮件地址的最大长度以实现最大的兼容性。

 TABLE sessions: session_id - varchar(?), PK session_token - varchar(?), NOT NULL session_data - MediumText, NOT NULL 

请注意,此表中没有用户字段,因为登录时的用户名在会话数据中,且程序不允许空数据。 session_id和session_token可以使用随机md5哈希值,sha1 / 128/256哈希值,日期时间戳和随机字符串添加到哈希函数或任何您想要的值来生成,但是输出的熵应该尽可能高缓解暴力攻击,甚至在离开地面之前,应该在会话表中检查所有由会话类生成的哈希,然后再尝试添加它们。

 TABLE autologin: UID - auto increment, PK username - varchar(255), NOT NULL, allow duplicates hostname - varchar(255), NOT NULL, allow duplicates mac_address - char(23), NOT NULL, unique token - varchar(?), NOT NULL, allow duplicates expires - datetime code 

MAC地址本质上应该是唯一的,因此每个条目都有独特的价值是有道理的。 另一方面,主机名可以合法地在不同的网络上复制。 有多少人使用“Home-PC”作为他们的计算机名称之一? 用户名是由服务器后端从会话数据中提取的,所以操作是不可能的。 至于令牌,同样的方法来生成会话令牌的页面应该被用来在cookie中生成令牌用于用户自动签名。 最后,当用户需要重新验证他们的证书时,添加日期时间代码。 在用户登录后几天内更新这个日期时间,或者强制它到期,无论上次登录只保留一个月左右,以设计为准。

这可以防止某人系统地欺骗他们知道自动签名的用户的MAC和主机名。 绝对不要让用户用自己的密码,明文或其他方式保存cookie。 让每个页面导航都重新生成令牌,就像会话令牌一样。 这大大降低了攻击者获得有效令牌cookie并使用它登录的可能性。 有些人会试图说,攻击者可以从受害者那里窃取cookie,并进行会话重播攻击来登录。 如果攻击者可以窃取cookie(这是可能的),他们肯定会损害整个设备,这意味着他们可以只使用设备登录,这完全破坏了cookie的目的。 只要您的站点通过HTTPS(在处理密码,CC号码或其他登录系统时运行),您就可以在浏览器中为用户提供所有的保护。

有一点要记住:会话数据不应该过期,如果你使用自动登录。 您可以过期错误地继续会话的能力,但验证到系统中应该恢复会话数据,如果它是预期在会话之间继续的持久数据。 如果你想持久性和非持久性会话数据,请使用另一个表作为持久性会话数据,使用用户名作为PK,并让服务器像检查正常会话数据一样检索它,只需使用另一个变量即可。

一旦以这种方式实现登录,服务器应该仍然验证会话。 这是您可以编码被盗或受损系统的期望值的地方; 模式和登录会话数据的其他预期结果通常会导致系统被劫持或cookie被伪造以获取访问权的结论。 这是ISS技术公司可以将触发账户锁定或自动从用户自动登录系统中删除的规则,让攻击者足够长的时间让用户确定攻击者是如何成功的,以及如何将其切断。

作为结束语,请确保任何恢复尝试,密码更改或超过阈值的登录失败都会导致自动登录被禁用,直到用户正确验证并确认已发生。

我很抱歉,如果有人希望在我的答案中发出代码,这不会发生在这里。 我会说,我使用PHP,jQuery和AJAX来运行我的网站,而我从来没有使用Windows作为服务器。

我的解决方案是这样的。 这不是100%防弹,但我认为它会为大多数情况下节省你。

当用户登录成功时,使用以下信息创建一个字符串:

 $data = (SALT + ":" + hash(User Agent) + ":" + username + ":" + LoginTimestamp + ":"+ SALT) 

加密$data ,将类型设置为HttpOnly并设置cookie。

当用户回到您的网站时,请执行以下步骤:

  1. 获取Cookie数据。 删除cookie内的危险字符。 爆炸它:字符。
  2. 检查有效性。 如果Cookie比X天早,则将用户重定向到登录页面。
  3. 如果cookie不老, 从数据库获取最新的密码更改时间。 如果密码在用户上次登录后改变,将用户重定向到登录页面。
  4. If pass wasn't changed recently; Get user's current browser agent. Check whether (currentUserAgentHash == cookieUserAgentHash). IF agents are same go to next step, else redirect to login page.
  5. If all steps passed successfully authorize username.

If user signouts, remove this cookie. Create new cookie if user re-logins.

I don't understand the concept of storing encrypted stuff in a cookie when it is the encrypted version of it that you need to do your hacking. If I'm missing something, please comment.

I am thinking about taking this approach to 'Remember Me'. If you can see any issues, please comment.

  1. Create a table to store "Remember Me" data in – separate to the user table so that I can log in from multiple devices.

  2. On successful login (with Remember Me ticked):

    a) Generate a unique random string to be used as the UserID on this machine: bigUserID

    b) Generate a unique random string: bigKey

    c) Store a cookie: bigUserID:bigKey

    d) In the "Remember Me" table, add a record with: UserID, IP Address, bigUserID, bigKey

  3. If trying to access something that requires login:

    a) Check for the cookie and search for bigUserID & bigKey with a matching IP address

    b) If you find it, Log the person in but set a flag in the user table "soft login" so that for any dangerous operations, you can prompt for a full login.

  4. On logout, Mark all the "Remember Me" records for that user as expired.

The only vulnerabilities that I can see is;

  • you could get hold of someone's laptop and spoof their IP address with the cookie.
  • you could spoof a different IP address each time and guess the whole thing – but with two big string to match, that would be…doing a similar calculation to above…I have no idea…huge odds?

I read all the answers and still found it difficult to extract what I was supposed to do. If a picture is worth 1k words I hope this helps others implement a secure persistent storage based on Barry Jaspan's Improved Persistent Login Cookie Best Practice

在这里输入图像描述

If you have questions, feedback, or suggestions, I will try to update the diagram to reflect for the newbie trying to implement a secure persistent login.

Implementing a "Keep Me Logged In" feature means you need to define exactly what that will mean to the user. In the simplest case, I would use that to mean the session has a much longer timeout: 2 days (say) instead of 2 hours. To do that, you will need your own session storage, probably in a database, so you can set custom expiry times for the session data. Then you need to make sure you set a cookie that will stick around for a few days (or longer), rather than expire when they close the browser.

I can hear you asking "why 2 days? why not 2 weeks?". This is because using a session in PHP will automatically push the expiry back. This is because a session's expiry in PHP is actually an idle timeout.

Now, having said that, I'd probably implement a harder timeout value that I store in the session itself, and out at 2 weeks or so, and add code to see that and to forcibly invalidate the session. Or at least to log them out. This will mean that the user will be asked to login periodically. 雅虎 does this.

Use some kind of "cookie" hash, the username + md5($ip, $username, $password) ? That would be my suggestion.

And if(md5($_SERVER['REMOTE_ADDR'], $username, $password) = $cookiehash) ? 🙂