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

时间:2009-08-30 21:50:01

标签: php security session remember-me

我的网络应用程序使用会话在用户登录后存储有关用户的信息,并在应用程序中从一个页面移动到另一个页面时维护该信息。在此特定应用程序中,我将存储此人的user_idfirst_namelast_name

我想在登录时提供“保持登录状态”选项,这将在用户的计算机上放置一个cookie两周,这将在他们返回应用程序时以相同的详细信息重新启动他们的会话。 / p>

这样做的最佳方法是什么?我不想将他们的user_id存储在cookie中,因为这似乎可以让一个用户轻松尝试伪造另一个用户的身份。

13 个答案:

答案 0 :(得分:685)

好的,让我直言不讳地说:如果您为此目的将用户数据或从用户数据派生的任何内容放入cookie中,那么您做错了。

有。我说了。现在我们可以转到实际答案。

您要问,散列用户数据有什么问题?嗯,它归结为曝光表面和通过默默无闻的安全性。

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

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

md5(salt+username+ip+salt)

现在,攻击者需要做的就是暴力破解“盐”(这不是真正的盐,但稍后会更多),现在他可以用他的任何用户名生成他想要的所有假代币。 IP地址!但强迫盐很难,对吧?绝对。但现代GPU非常擅长。除非你在它中使用足够的随机性(使它足够大),它会迅速下降,并随之成为你城堡的钥匙。

简而言之,唯一保护你的是盐,它并不像你想象的那样真正保护你。

但等等!

所有这一切都预示着攻击者知道算法!如果这是秘密和混乱,那么你是安全的,对吧?的 WRONG 即可。这种想法有一个名称:安全通过晦涩,应该从不依赖。

更好的方式

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

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

如果攻击者猜到另一个用户的随机令牌怎么办?

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

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

现在,为了说明这个数字是多么荒谬,让我们想象一下互联网上的每个服务器(比如今天的50,000,000)试图以每秒1,000,000,000的速率强行推断这个数字。实际上,你的服务器会在这样的负载下融化,但让我们来解决它。

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性别秒......

让我们尝试将其归结为更友好的数字。

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);
        }
    }
}

注意:请勿使用令牌或用户和令牌的组合来查找数据库中的记录。始终确保根据用户获取记录,并使用计时安全比较功能来比较之后获取的令牌。 More about timing attacks

现在,非常重要的是SECRET_KEY是加密秘密(由/dev/urandom生成和/或从高熵输入派生)。此外,GenerateRandomToken()需要是强大的随机来源(mt_rand()不够强大。请使用库,例如​​RandomLibrandom_compatmcrypt_create_iv()DEV_URANDOM)...

hash_equals()是为了阻止timing attacks。 如果您使用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;
}

答案 1 :(得分:94)

  

安全通知:根据确定性数据的MD5哈希来设置cookie是一个坏主意;最好使用从CSPRNG派生的随机令牌。有关更安全的方法,请参阅ircmaxell's answer此问题。

通常我会做这样的事情:

  1. 用户使用“让我登录”
  2. 登录
  3. 创建会话
  4. 创建一个名为SOMETHING的cookie,其中包含:md5(salt + username + ip + salt)和一个名为somethingElse的cookie,其中包含id
  5. 将Cookie存储在数据库中
  6. 用户做事和离开----
  7. 用户返回,检查somethingElse cookie,如果存在,则从数据库中获取该用户的旧哈希,检查cookie SOMETHING的内容是否与数据库中的哈希匹配,这也应该与新计算的哈希匹配(对于ip)因此:cookieHash == databaseHash == md5(salt + username + ip + salt),如果他们这样做,转到2,如果他们不转到1
  8. 当然,您可以使用不同的cookie名称等。您也可以稍微更改cookie的内容,只需确保不要轻易创建。例如,您还可以在创建用户时创建user_salt,并将其放入cookie中。

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

答案 2 :(得分:74)

<强>简介

您的头衔“让我登录” - 最好的方法让我很难知道从哪里开始,因为如果您正在寻找最佳方法,那么您将不得不考虑以下因素: / p>

  • 鉴定
  • 安全

<强>缓存

Cookie易受攻击,在常见的浏览器Cookie-theft漏洞和跨站点脚本攻击之间,我们必须接受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方法生成键控哈希值)

简单方法

一个简单的解决方案是:

  • 用户已使用“记住我”
  • 登录
  • 使用令牌&amp;发布的登录Cookie签名
  • 何时返回,检查签名
  • 如果签名没问题,那么用户名&amp;在数据库中查找令牌
  • 如果无效..返回登录页面
  • 如果有效则自动登录

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

  • 无法知道饼干是否被盗
  • 攻击者可能是访问敏感操作,如更改密码或个人信息和烘焙信息等数据。
  • 受损的Cookie对Cookie生命周期仍然有效

更好的解决方案

更好的解决方案是

  • 用户已登录并记住我已被选中
  • 生成令牌&amp;签名和存储在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中测试&amp; Chrome

enter image description here

优势

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

缺点

  • 不支持通过多个浏览器(移动和网络)进行持久连接
  • Cookie仍然可以被盗,因为用户只能在下次登录后收到通知。

快速修复

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

多Cookie方法

当攻击者即将窃取cookie时,只关注特定网站或域名,例如。的 example.com

但实际上,您可以对来自2个不同域( example.com &amp; fakeaddsite.com )的用户进行身份验证,并使其看起来像&#34;广告Cookie&# 34;

  • 用户登录 example.com 并记住我
  • 存储用户名,令牌,Cookie中的引用
  • 在数据库中存储用户名,令牌,引用,例如。 Memcache
  • 通过get和iframe将引用ID发送到 fakeaddsite.com
  • fakeaddsite.com使用引用来获取用户&amp;来自数据库的令牌
  • fakeaddsite.com存储签名
  • 当用户从fakeaddsite.com
  • 返回使用iframe获取签名信息时
  • 合并数据并进行验证
  • .....你知道剩下的

有些人可能想知道你如何使用2种不同的饼干?好吧,想象一下example.com = localhostfakeaddsite.com = 192.168.1.120。如果您检查cookie,它将看起来像这样

enter image description here

从上图

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

192.168.1.120

  • 仅接受已定义的HTTP_REFERER
  • 仅接受来自指定REMOTE_ADDR
  • 的连接
  • 没有JavaScript,没有任何内容,只包含标志信息,并从Cookie中添加或检索

优势

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

缺点

  • 仅针对单次登录的多个服务器请求

改进

  • 完成使用iframe使用ajax

答案 3 :(得分:24)

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

答案 4 :(得分:6)

我问过这个问题的一个角度here,答案会引导您找到所需的所有基于令牌的超时Cookie链接。

基本上,您不会将userId存储在cookie中。您存储一次性令牌(大字符串),用户用它来获取旧的登录会话。然后为了使其真正安全,您需要密码来进行繁重的操作(比如更改密码本身)。

答案 5 :(得分:5)

我会推荐Stefan提到的方法(即遵循Improved Persistent Login Cookie Best Practice中的指导原则),并建议您确保您的Cookie为HttpOnly cookies,以便它们无法访问,可能是恶意的JavaScript。

答案 6 :(得分:4)

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

答案 7 :(得分:4)

旧线程,但仍然是一个有效的关注点。我注意到一些关于安全性的好的回答,并且避免通过默默无闻来使用“安全性”,但实际的技术方法在我眼中是不够的。在我提出方法之前我必须说的话:

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

尽管如此,有两种方法可以在您的系统上进行自动登录。

首先,廉价,简单的方式将其全部放在别人身上。如果您使用谷歌+帐户登录您的网站支持,您可能有一个简化的谷歌+按钮,如果他们已经登录谷歌,将记录用户(我这样做是为了回答这个问题,因为我总是签到谷歌)。如果您希望用户自动登录,如果他们已经使用受信任和支持的身份验证器登录,并选中了复选框,请让您的客户端脚本执行相应的&#39;登录后面的代码&# 39;加载前按钮,只需确保服务器在自动登录表中存储唯一ID,该表具有用户名,会话ID和用户身份验证器。由于这些登录方法使用AJAX,因此您无论如何都在等待响应,并且该响应是经过验证的响应或拒绝。如果您获得经过验证的响应,请正常使用它,然后继续正常加载登录用户。否则,登录失败,但不告诉用户,只要继续登录,他们就会注意到。这是为了防止攻击者窃取cookie(或伪造他们以试图升级特权),以防止用户自动登录该网站。

这很便宜,也可能被一些人视为肮脏,因为它试图验证您可能已经在Google和Facebook这样的地方签名,甚至没有告诉您。但是,它不应该用于未要求自动登录您网站的用户,并且此特定方法仅适用于外部身份验证,例如Google+或FB。

由于外部身份验证器用于在后台告知服务器用户是否经过验证,因此攻击者无法获得除唯一ID之外的任何内容,而该ID本身无用。我将详细说明:

  • 用户&#39; joe&#39;第一次访问网站,会话ID放在Cookie&#39;会话&#39;。
  • 用户&#39; joe&#39;登录,升级权限,获取新的会话ID并续订Cookie&#39;会话&#39;。
  • 用户&#39; joe&#39;选择使用谷歌+自动登录,获取一个放在cookie中的唯一ID&#39; keepmesignedin&#39;。
  • 用户&#39; joe&#39;谷歌已让他们登录,允许您的网站在后端使用谷歌自动登录用户。
  • 攻击者会系统地尝试使用唯一ID来保持&#39; keepmesignin&#39; (这是向每个用户发布的公共知识),并且没有签到任何其他地方;尝试给予&#39; joe&#39;。
  • 的唯一ID
  • 服务器收到&#39; joe&#39;的唯一ID,在Google中为Google +帐户提取匹配。
  • 服务器将攻击者发送到登录页面,该登录页面运行AJAX请求以谷歌登录。
  • Google服务器接收请求,使用其API查看当前未登录攻击者。
  • Google通过此连接发送当前没有登录用户的回复。
  • 攻击者的页面收到响应,脚本会自动重定向到登录页面,并在网址中编码POST值。
  • 登录页面获取POST值,发送&#39; keepmesignedin&#39;到空值和有效期至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地址本质上应该是独一无二的,因此每个条目都具有唯一值是有道理的。另一方面,主机名可以合法地在不同的网络上复制。有多少人使用&#34; Home-PC&#34;作为他们的计算机名称之一?用户名是由服务器后端从会话数据中获取的,因此操作它是不可能的。至于令牌,应该使用相同的方法为页面生成会话令牌,以便为用户自动登录生成cookie中的令牌。最后,添加日期时间代码以供用户在需要重新验证其凭据时使用。要么在用户登录时更新此日期时间,要么在几天内将其保留,要么强制它过期,无论上次登录只保留一个月左右,无论您的设计要求如何。

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

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

以这种方式实现登录后,服务器仍应验证会话。您可以在此处编制对被盗或受损系统的期望;登录到会话数据的模式和其他预期结果通常可以得出结论,系统被​​劫持或cookie被伪造以获得访问权。这是您的ISS技术可以制定​​规则,可以触发帐户锁定或自动从自动登录系统中删除用户,使攻击者远离攻击者,以便用户确定攻击者如何成功以及如何将其删除。

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

如果有人希望在我的回答中提供代码,我很抱歉,这不会发生在这里。我会说我使用PHP,jQuery和AJAX来运行我的网站,我永远不会使用Windows作为服务器......

答案 8 :(得分:2)

我的解决方案是这样的。它不是100%防弹,但我认为它可以为你节省大部分时间。

当用户登录成功创建包含此信息的字符串时:

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

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

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

  1. 获取Cookie数据。删除cookie中的危险字符。用:字符爆炸它。
  2. 检查有效性。如果cookie超过X天,则将用户重定向到登录页面。
  3. 如果饼干不老;从数据库获取最新的密码更改时间。如果在用户上次登录后将密码更改为将用户重定向到登录页面。
  4. 如果最近没有改变通行证;获取用户当前的浏览器代理。检查是否(currentUserAgentHash == cookieUserAgentHash)。如果代理商是相同的,请转到下一步,否则重定向到登录页面。
  5. 如果成功通过所有步骤,则授权用户名。
  6. 如果用户退出,请删除此Cookie。如果用户重新登录,则创建新的cookie。

答案 9 :(得分:2)

我不理解将加密的东西存储在cookie中的概念,当它是你需要进行黑客攻击的加密版本时。如果我遗漏了什么,请发表评论。

我正在考虑采用这种方法来“记住我”。如果您发现任何问题,请发表评论。

  1. 创建一个表来存储“记住我”数据 - 与用户表分开,以便我可以从多个设备登录。

  2. 成功登录后(记住我勾选):

    a)生成一个唯一的随机字符串,用作此机器上的UserID:bigUserID

    b)生成一个唯一的随机字符串:bigKey

    c)存储cookie:bigUserID:bigKey

    d)在“记住我”表格中,添加一条记录:UserID,IP Address,bigUserID,bigKey

  3. 如果尝试访问需要登录的内容:

    a)检查cookie并搜索bigUserID&amp;具有匹配IP地址的bigKey

    b)如果找到它,请记录此人,但在用户表“软登录”中设置一个标志,以便对于任何危险操作,您可以提示完整登录。

  4. 注销时,将该用户的所有“记住我”记录标记为已过期。

  5. 我能看到的唯一漏洞是

    • 你可以抓住某人的笔记本电脑并用cookie欺骗他们的IP地址。
    • 你可以每次欺骗一个不同的IP地址并猜测整个事情 - 但是要匹配两个大字符串,那就是......对上面进行类似的计算...我不知道......巨大的赔率?

答案 10 :(得分:2)

我阅读了所有的答案,但仍然发现很难提取我应该做的事情。如果图片价值1k字,我希望这可以帮助其他人实现基于Barry Jaspan Improved Persistent Login Cookie Best Practice

的安全持久存储

enter image description here

如果您有任何问题,反馈或建议,我会尝试更新图表,以反映尝试实施安全持久登录的新手。

答案 11 :(得分:0)

实施“保持登录状态”功能意味着您需要准确定义对用户意味着什么。在最简单的情况下,我会用它来表示会话有更长的超时:2天(比方说)而不是2小时。为此,您可能需要自己的会话存储(可能在数据库中),因此您可以为会话数据设置自定义到期时间。然后,您需要确保设置一个cookie,它会在几天(或更长时间)内停留,而不是在关闭浏览器时过期。

我可以听到你问“为什么2天?为什么不是2周?”。这是因为在PHP中使用会话将自动推迟到期。这是因为PHP中的会话到期实际上是空闲超时。

现在,说到这一点,我可能会实现一个更难的超时值,我存储在会话本身,并在2周左右,并添加代码来查看并强制使会话无效。或者至少将它们记录下来。这将意味着将要求用户定期登录。雅虎这样做。

答案 12 :(得分:0)

我认为您可以这样做:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

$cookiestring存储在数据库中,并将其设置为cookie。还要将该人的用户名设置为cookie。哈希的全部要点是它不能被反向工程。

当用户出现时,从一个cookie获取用户名,而不是从另一个$cookieString获取用户名。如果$cookieString与数据库中存储的匹配,则对用户进行身份验证。由于password_hash每次都使用不同的盐,因此与明文内容无关。