“让我保持登录状态” - 最好的方法

发布于 2024-08-02 22:13:22 字数 1700 浏览 8 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

忘你却要生生世世 2024-08-09 22:13:22

好吧,让我直白地说:如果您出于此目的将用户数据或从用户数据派生的任何内容放入 cookie,那么您就做错了。

那里。我说过了。现在我们可以继续讨论实际的答案。

您可能会问,对用户数据进行哈希处理有什么问题吗?好吧,这归结为暴露表面和通过默默无闻实现的安全性。

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

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

md5(salt+username+ip+salt)

现在,攻击者所需要做的就是暴力破解“盐”(这并不是真正的盐,稍后会详细介绍),并且他现在可以使用他的任何用户名生成他想要的所有假令牌IP地址!但是暴力破解盐很难,对吧?绝对地。但现代 GPU 非常擅长于此。除非你在其中使用足够的随机性(使其足够大),否则它会很快掉落,你的城堡的钥匙也会随之掉落。

简而言之,唯一能保护你的是盐,它并没有你想象的那么真正保护你。

但是等等!

所有这一切都是基于攻击者知道算法!如果它是秘密且令人困惑的,那么你就安全了,对吗? 错误。这种思路有一个名字:通过默默无闻实现安全永远不应该依赖它。

更好的方法

更好的方法是永远不要让用户的信息离开服务器,除了 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);
        }
    }
}

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

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

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

/**
 * 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;
}

OK, let me put this bluntly: if you're putting user data, or anything derived from user data into a cookie for this purpose, you're doing something wrong.

There. I said it. Now we can move on to the actual answer.

What's wrong with hashing user data, you ask? Well, it comes down to exposure surface and security through obscurity.

Imagine for a second that you're an attacker. You see a cryptographic cookie set for the remember-me on your session. It's 32 characters wide. Gee. That may be an MD5...

Let's also imagine for a second that they know the algorithm that you used. For example:

md5(salt+username+ip+salt)

Now, all an attacker needs to do is brute force the "salt" (which isn't really a salt, but more on that later), and he can now generate all the fake tokens he wants with any username for his IP address! But brute-forcing a salt is hard, right? Absolutely. But modern day GPUs are exceedingly good at it. And unless you use sufficient randomness in it (make it large enough), it's going to fall quickly, and with it the keys to your castle.

In short, the only thing protecting you is the salt, which isn't really protecting you as much as you think.

But Wait!

All of that was predicated that the attacker knows the algorithm! If it's secret and confusing, then you're safe, right? WRONG. That line of thinking has a name: Security Through Obscurity, which should NEVER be relied upon.

The Better Way

The better way is to never let a user's information leave the server, except for the id.

When the user logs in, generate a large (128 to 256 bit) random token. Add that to a database table which maps the token to the userid, and then send it to the client in the cookie.

What if the attacker guesses the random token of another user?

Well, let's do some math here. We're generating a 128 bit random token. That means that there are:

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

Now, to show how absurdly large that number is, let's imagine every server on the internet (let's say 50,000,000 today) trying to brute-force that number at a rate of 1,000,000,000 per second each. In reality your servers would melt under such load, but let's play this out.

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

So 50 quadrillion guesses per second. That's fast! Right?

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

So 6.8 sextillion seconds...

Let's try to bring that down to more friendly numbers.

215,626,585,489,599 years

Or even better:

47917 times the age of the universe

Yes, that's 47917 times the age of the universe...

Basically, it's not going to be cracked.

So to sum up:

The better approach that I recommend is to store the cookie with three parts.

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

Then, to validate:

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

Note: Do not use the token or combination of user and token to lookup a record in your database. Always be sure to fetch a record based on the user and use a timing-safe comparison function to compare the fetched token afterwards. More about timing attacks.

Now, it's very important that the SECRET_KEY be a cryptographic secret (generated by something like /dev/urandom and/or derived from a high-entropy input). Also, GenerateRandomToken() needs to be a strong random source (mt_rand() is not nearly strong enough. Use a library, such as RandomLib or random_compat, or mcrypt_create_iv() with DEV_URANDOM)...

The hash_equals() is to prevent timing attacks.
If you use a PHP version below PHP 5.6 the function hash_equals() is not supported. In this case you can replace hash_equals() with the timingSafeCompare function:

/**
 * 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;
}
苏璃陌 2024-08-09 22:13:22

安全声明:基于确定性数据的 MD5 哈希值构建 cookie 是一个坏主意;最好使用从 CSPRNG 派生的随机令牌。请参阅 ircmaxell 对此问题的回答,了解更安全的方法。

通常我会这样做:

  1. 用户使用“保持登录状态”登录
  2. 创建会话
  3. 创建一个名为“SOMETHING”的 cookie,其中包含:md5(salt+username+ip+salt) 和一个名为“somethingElse”的 cookie,其中包含 id
  4. 在数据库中存储 cookie
  5. 用户执行操作并离开 ----
  6. 用户返回,检查 someElse cookie,如果存在,则从数据库中获取该用户的旧哈希值,检查 cookie 的内容是否与数据库中的哈希值匹配,数据库中的哈希值也应该与新计算的哈希值(对于ip)因此:cookieHash==databaseHash==md5(salt+username+ip+salt),如果是,则转到2,如果不转到1

当然你可以使用不同的cookie名称等您还可以稍微更改 cookie 的内容,只要确保它不会轻易创建即可。例如,您还可以在创建用户时创建 user_salt 并将其放入 cookie 中。

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

Security Notice: Basing the cookie off an MD5 hash of deterministic data is a bad idea; it's better to use a random token derived from a CSPRNG. See ircmaxell's answer to this question for a more secure approach.

Usually I do something like this:

  1. User logs in with 'keep me logged in'
  2. Create session
  3. Create a cookie called SOMETHING containing: md5(salt+username+ip+salt) and a cookie called somethingElse containing id
  4. Store cookie in database
  5. User does stuff and leaves ----
  6. User returns, check for somethingElse cookie, if it exists, get the old hash from the database for that user, check of the contents of cookie SOMETHING match with the hash from the database, which should also match with a newly calculated hash (for the ip) thus: cookieHash==databaseHash==md5(salt+username+ip+salt), if they do, goto 2, if they don't goto 1

Of course you can use different cookie names etc. also you can change the content of the cookie a bit, just make sure it isn't to easily created. You can for example also create a user_salt when the user is created and also put that in the cookie.

Also you could use sha1 instead of md5 (or pretty much any algorithm)

情话难免假 2024-08-09 22:13:22

简介

您的标题“让我保持登录状态”- 最佳方法让我很难知道从哪里开始,因为如果您正在寻找最佳方法,那么您必须考虑以下因素:

  • 识别
  • 安全

Cookie

Cookie 很容易受到攻击,在常见的浏览器 cookie 盗窃漏洞和跨站点脚本攻击之间,我们必须承认 cookie 并不安全。为了帮助提高安全性,您必须注意 php setcookies 还有其他功能,例如

bool setcookie ( 字符串 $name [, 字符串 $value [, int $expire = 0 [, 字符串 $path [, 字符串 $domain [, 布尔 $secure = false [, 布尔 $httponly = false ]]]]]] )< /p>

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

定义

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

简单方法

一个简单的解决方案是:

  • “记住我登录 Cookie”进行登录
  • 用户使用带有令牌和密钥的 。签名
  • 返回时,检查签名
  • 如果签名正常..则用户名和密码 在数据库中查找令牌
  • 如果无效,则
  • ..返回登录页面如果有效,则自动登录

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

  • 无法知道 cookie 是否被盗
  • 攻击者可能会访问敏感操作,例如更改密码或个人和烘焙信息等数据。
  • 受损的 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 和 Firefox 中进行测试Chrome

在此处输入图像描述

优点

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

缺点

  • 不支持通过多个浏览器(移动和 Web)的持久连接
  • cookie 可以仍然会被盗,因为用户只有在下次登录后才会收到通知。

快速修复

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

多 Cookie 方法

当攻击者要窃取 cookie 时,唯一关注的是特定网站或域,例如。 example.com

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

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

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

在此处输入图像描述

从上图中

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

192.168.1.120

  • 仅接受定义的 HTTP_REFERER
  • 仅接受来自指定 REMOTE_ADDR 的连接
  • 无 JavaScript,无内容,但不包含任何内容,而不是签名信息并添加或检索它来自 cookie

优点

  • 99% 的时间你都可以欺骗攻击者
  • 您可以在攻击者第一次尝试时轻松锁定帐户,
  • 甚至可以像其他方法一样在下次登录之前阻止攻击

缺点

  • 一次登录即可向服务器发出多个请求

改进

  • 完成使用 iframe 使用 ajax

Introduction

Your title “Keep Me Logged In” - the best approach make it difficult for me to know where to start because if you are looking at best approach then you would have to consideration the following :

  • Identification
  • Security

Cookies

Cookies are vulnerable, Between common browser cookie-theft vulnerabilities and cross-site scripting attacks we must accept that cookies are not safe. To help improve security you must note that php setcookies has additional functionality such as

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

  • secure (Using HTTPS connection)
  • httponly (Reduce identity theft through XSS attack)

Definitions

  • Token ( Unpredictable random string of n length eg. /dev/urandom)
  • Reference ( Unpredictable random string of n length eg. /dev/urandom)
  • Signature (Generate a keyed hash value using the HMAC method)

Simple Approach

A simple solution would be :

  • User is logged on with Remember Me
  • Login Cookie issued with token & Signature
  • When is returning, Signature is checked
  • If Signature is ok .. then username & token is looked up in the database
  • if not valid .. return to login page
  • If valid automatically login

The above case study summarizes all example given on this page but they disadvantages is that

  • There is no way to know if the cookies was stolen
  • Attacker may be access sensitive operations such as change of password or data such as personal and baking information etc.
  • The compromised cookie would still be valid for the cookie life span

Better Solution

A better solution would be

  • User is logged in and remember me is selected
  • Generate Token & signature and store in cookie
  • The tokens are random and are only valid for single autentication
  • The token are replace on each visit to the site
  • When a non-logged user visit the site the signature, token and username are verified
  • Remember me login should have limited access and not allow modification of password, personal information etc.

Example Code

// 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 Used

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

Testing in Firefox & Chrome

enter image description here

Advantage

  • Better Security
  • Limited access for attacker
  • When cookie is stolen its only valid for single access
  • When next the original user access the site you can automatically detect and notify the user of theft

Disadvantage

  • Does not support persistent connection via multiple browser (Mobile & Web)
  • The cookie can still be stolen because the user only gets the notification after the next login.

Quick Fix

  • Introduction of approval system for each system that must have persistent connection
  • Use multiple cookies for the authentication

Multiple Cookie Approach

When an attacker is about to steal cookies the only focus it on a particular website or domain eg. example.com

But really you can authenticate a user from 2 different domains (example.com & fakeaddsite.com) and make it look like "Advert Cookie"

  • User Logged on to example.com with remember me
  • Store username, token, reference in cookie
  • Store username, token, reference in Database eg. Memcache
  • Send refrence id via get and iframe to fakeaddsite.com
  • fakeaddsite.com uses the reference to fetch user & token from Database
  • fakeaddsite.com stores the signature
  • When a user is returning fetch signature information with iframe from fakeaddsite.com
  • Combine it data and do the validation
  • ..... you know the remaining

Some people might wonder how can you use 2 different cookies ? Well its possible, imagine example.com = localhost and fakeaddsite.com = 192.168.1.120. If you inspect the cookies it would look like this

enter image description here

From the image above

  • The current site visited is localhost
  • It also contains cookies set from 192.168.1.120

192.168.1.120

  • Only accepts defined HTTP_REFERER
  • Only accepts connection from specified REMOTE_ADDR
  • No JavaScript, No content but consist nothing rather than sign information and add or retrieve it from cookie

Advantage

  • 99% percent of the time you have tricked the attacker
  • You can easily lock the account in the attacker first attempt
  • Attack can be prevented even before the next login like the other methods

Disadvantage

  • Multiple Request to server just for a single login

Improvement

  • Done use iframe use ajax
染火枫林 2024-08-09 22:13:22

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

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

综上所述,有两种在系统上实现自动登录的好方法。

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

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

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

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

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

对于使用外部身份验证器登录您的站点的用户来说,此方法可以而且应该与您的内部身份验证器结合使用。

=========

现在,对于您自己的可以自动登录用户的身份验证器系统,我是这样做的:

数据库有几个表:

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

注意,这个表中没有user字段,因为登录时的用户名是在会话数据中的,程序不允许空数据。 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 作为服务器......永远。

Old thread, but still a valid concern. I noticed some good responses about security, and avoiding use of 'security through obscurity', but the actual technical methods given were not sufficient in my eyes. Things I must say before I contribute my method:

  • NEVER store a password in clear text...EVER!
  • NEVER store a user's hashed password in more than one location in your database. Your server backend is always capable of pulling the hashed password from the users table. It's not more efficient to store redundant data in lieu of additional DB transactions, the inverse is true.
  • Your Session ID's should be unique, so no two users could ever share an ID, hence the purpose of an ID (could your Driver's License ID number ever match another persons? No.) This generates a two-piece unique combination, based on 2 unique strings. Your Sessions table should use the ID as the PK. To allow multiple devices to be trusted for auto-signin, use another table for trusted devices which contains the list of all validated devices (see my example below), and is mapped using the username.
  • It serves no purpose to hash known data into a cookie, the cookie can be copied. What we are looking for is a complying user device to provide authentic information that cannot be obtained without an attacker compromising the user's machine (again, see my example). This would mean, however, that a legitimate user who forbids his machine's static information (i.e. MAC address, device hostname, useragent if restricted by browser, etc.) from remaining consistent (or spoofs it in the first place) will not be able to use this feature. But if this is a concern, consider the fact that you are offering auto-signin to users whom identify themselves uniquely, so if they refuse to be known by spoofing their MAC, spoofing their useragent, spoofing/changing their hostname, hiding behind proxies, etc., then they are not identifiable, and should never be authenticated for an automatic service. If you want this, you need to look into smart-card access bundled with client-side software that establishes identity for the device being used.

That all being said, there are two great ways to have auto-signin on your system.

First, the cheap, easy way that puts it all on someone else. If you make your site support logging in with, say, your google+ account, you probably have a streamlined google+ button that will log the user in if they are already signed into google (I did that here to answer this question, as I am always signed into google). If you want the user automatically signed in if they are already signed in with a trusted and supported authenticator, and checked the box to do so, have your client-side scripts perform the code behind the corresponding 'sign-in with' button before loading, just be sure to have the server store a unique ID in an auto-signin table that has the username, session ID, and the authenticator used for the user. Since these sign-in methods use AJAX, you are waiting for a response anyway, and that response is either a validated response or a rejection. If you get a validated response, use it as normal, then continue loading the logged in user as normal. Otherwise, the login failed, but don't tell the user, just continue as not logged in, they will notice. This is to prevent an attacker who stole cookies (or forged them in an attempt to escalate privileges) from learning that the user auto-signs into the site.

This is cheap, and might also be considered dirty by some because it tries to validate your potentially already signed in self with places like Google and Facebook, without even telling you. It should, however, not be used on users who have not asked to auto-signin your site, and this particular method is only for external authentication, like with Google+ or FB.

Because an external authenticator was used to tell the server behind the scenes whether or not a user was validated, an attacker cannot obtain anything other than a unique ID, which is useless on its own. I'll elaborate:

  • User 'joe' visits site for first time, Session ID placed in cookie 'session'.
  • User 'joe' Logs in, escalates privileges, gets new Session ID and renews cookie 'session'.
  • User 'joe' elects to auto-signin using google+, gets a unique ID placed in cookie 'keepmesignedin'.
  • User 'joe' has google keep them signed in, allowing your site to auto-signin the user using google in your backend.
  • Attacker systematically tries unique IDs for 'keepmesignedin' (this is public knowledge handed out to every user), and is not signed into anywhere else; tries unique ID given to 'joe'.
  • Server receives Unique ID for 'joe', pulls match in DB for a google+ account.
  • Server sends Attacker to login page that runs an AJAX request to google to login.
  • Google server receives request, uses its API to see Attacker is not logged in currently.
  • Google sends response that there is no currently signed in user over this connection.
  • Attacker's page receives response, script automatically redirects to login page with a POST value encoded in the url.
  • Login page gets the POST value, sends the cookie for 'keepmesignedin' to an empty value and a valid until date of 1-1-1970 to deter an automatic attempt, causing the Attacker's browser to simply delete the cookie.
  • Attacker is given normal first-time login page.

No matter what, even if an attacker uses an ID that does not exist, the attempt should fail on all attempts except when a validated response is received.

This method can and should be used in conjunction with your internal authenticator for those who sign into your site using an external authenticator.

=========

Now, for your very own authenticator system that can auto-signin users, this is how I do it:

DB has a few tables:

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

Note that the username is capable of being 255 characters long. I have my server program limit usernames in my system to 32 characters, but external authenticators might have usernames with their @domain.tld be larger than that, so I just support the maximum length of an email address for maximum compatibility.

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

Note that there is no user field in this table, because the username, when logged in, is in the session data, and the program does not allow null data. The session_id and the session_token can be generated using random md5 hashes, sha1/128/256 hashes, datetime stamps with random strings added to them then hashed, or whatever you would like, but the entropy of your output should remain as high as tolerable to mitigate brute-force attacks from even getting off the ground, and all hashes generated by your session class should be checked for matches in the sessions table prior to attempting to add them.

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 addresses by their nature are supposed to be UNIQUE, therefore it makes sense that each entry has a unique value. Hostnames, on the other hand, could be duplicated on separate networks legitimately. How many people use "Home-PC" as one of their computer names? The username is taken from the session data by the server backend, so manipulating it is impossible. As for the token, the same method to generate session tokens for pages should be used to generate tokens in cookies for the user auto-signin. Lastly, the datetime code is added for when the user would need to revalidate their credentials. Either update this datetime on user login keeping it within a few days, or force it to expire regardless of last login keeping it only for a month or so, whichever your design dictates.

This prevents someone from systematically spoofing the MAC and hostname for a user they know auto-signs in. NEVER have the user keep a cookie with their password, clear text or otherwise. Have the token be regenerated on each page navigation, just as you would the session token. This massively reduces the likelihood that an attacker could obtain a valid token cookie and use it to login. Some people will try to say that an attacker could steal the cookies from the victim and do a session replay attack to login. If an attacker could steal the cookies (which is possible), they would certainly have compromised the entire device, meaning they could just use the device to login anyway, which defeats the purpose of stealing cookies entirely. As long as your site runs over HTTPS (which it should when dealing with passwords, CC numbers, or other login systems), you have afforded all the protection to the user that you can within a browser.

One thing to keep in mind: session data should not expire if you use auto-signin. You can expire the ability to continue the session falsely, but validating into the system should resume the session data if it is persistent data that is expected to continue between sessions. If you want both persistent AND non-persistent session data, use another table for persistent session data with the username as the PK, and have the server retrieve it like it would the normal session data, just use another variable.

Once a login has been achieved in this way, the server should still validate the session. This is where you can code expectations for stolen or compromised systems; patterns and other expected results of logins to session data can often lead to conclusions that a system was hijacked or cookies were forged in order to gain access. This is where your ISS Tech can put rules that would trigger an account lockdown or auto-removal of a user from the auto-signin system, keeping attackers out long enough for the user to determine how the attacker succeeded and how to cut them off.

As a closing note, be sure that any recovery attempt, password changes, or login failures past the threshold result in auto-signin being disabled until the user validates properly and acknowledges this has occurred.

I apologize if anyone was expecting code to be given out in my answer, that's not going to happen here. I will say that I use PHP, jQuery, and AJAX to run my sites, and I NEVER use Windows as a server... ever.

怪异←思 2024-08-09 22:13:22

在这里问了这个问题的一个角度,并且答案将引导您找到您需要的所有基于令牌的超时 cookie 链接。

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

I asked one angle of this question here, and the answers will lead you to all the token-based timing-out cookie links you need.

Basically, you do not store the userId in the cookie. You store a one-time token (huge string) which the user uses to pick-up their old login session. Then to make it really secure, you ask for a password for heavy operations (like changing the password itself).

人海汹涌 2024-08-09 22:13:22

我会推荐 Stefan 提到的方法(即遵循 改进的持久登录 Cookie 最佳实践中的指南),并建议您确保您的 Cookie 是 HttpOnly cookie JavaScript 无法访问它们,可能是恶意的。

I would recommend the approach mentioned by Stefan (i.e. follow the guidelines in Improved Persistent Login Cookie Best Practice) and also recommend that you make sure your cookies are HttpOnly cookies so they are not accessible to, potentially malicious, JavaScript.

玉环 2024-08-09 22:13:22

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

Generate a hash, maybe with a secret only you know, then store it in your DB so it can be associated with the user. Should work quite well.

没有心的人 2024-08-09 22:13:22

当您需要进行黑客攻击时,我不明白将加密内容存储在 cookie 中的概念。如果我遗漏了什么,请评论。

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

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

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

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

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

    c) 存储 cookie:bigUserID:bigKey

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

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

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

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

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

我能看到的唯一漏洞是;

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

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?
巴黎盛开的樱花 2024-08-09 22:13:22

我阅读了所有答案,但仍然发现很难提取出我应该做的事情。如果一张图片值 1000 个单词,我希望这可以帮助其他人基于 Barry Jaspan 的改进的持久登录 Cookie 最佳实践<实现安全持久存储/a>

“在此处输入图像描述”"

如果您有疑问、反馈或建议,我将尝试更新图表以反映试图实现安全持久登录的新手。

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

enter image description here

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.

私野 2024-08-09 22:13:22

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

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

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

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

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

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

如果用户退出,请删除此 cookie。如果用户重新登录,则创建新的 cookie。

My solution is like this. It's not 100% bulletproof but I think it will save you for the most of the cases.

When user logged in successfully create a string with this information:

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

Encrypt $data, set type to HttpOnly and set cookie.

When user come back to your site, Make this steps:

  1. Get cookie data. Remove dangerous characters inside cookie. Explode it with : character.
  2. Check validity. If cookie is older than X days then redirect user to login page.
  3. If cookie is not old; Get latest password change time from database. If password is changed after user's last login redirect user to login page.
  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.

陌路终见情 2024-08-09 22:13:22

我认为你可以这样做:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

$cookiestring 存储在数据库中并将其设置为 cookie。还将该人的用户名设置为 cookie。哈希的全部意义在于它不能被逆向工程。

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

I think you could just do this:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Store $cookiestring in the DB and and set it as a cookie. Also set the username of the person as a cookie. The whole point of a hash is that it can't be reverse-engineered.

When a user turns up, get the username from one cookie, than $cookieString from another. If $cookieString matches the one stored in the DB, then the user is authenticated. As password_hash uses a different salt each time, it is irrelevant as to what the clear text is.

暮年慕年 2024-08-09 22:13:22

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

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

现在,话虽如此,我可能会实现一个更严格的超时值,将其存储在会话本身中,并在两周左右的时间后退出,并添加代码来查看该值并强制使会话无效。或者至少将它们注销。这意味着用户将被要求定期登录。雅虎!这样做。

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. Yahoo! does this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文