关于php:“让我登录” – 最好的方法

“Keep Me Logged In” - the best approach

我的Web应用程序使用会话在用户登录后存储有关用户的信息,并在应用程序中的页面间传输时维护这些信息。在这个特定的应用程序中,我存储的是此人的user_idfirst_namelast_name

我想在登录时提供一个"让我保持登录"选项,它将在用户的机器上放置一个cookie两周,当他们返回应用程序时,该选项将以相同的详细信息重新启动会话。

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


好吧,让我直截了当地说:如果您要将用户数据或从用户数据派生的任何东西放入cookie中,那么您就做错了什么。

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

你问,散列用户数据有什么问题?好吧,它归结为暴露表面和安全通过模糊。

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

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

1
md5(salt+username+ip+salt)

现在,攻击者所需要做的就是对"salt"使用暴力(这不是真正的salt,但稍后会详细介绍),现在他可以用任何用户名生成所有他想要的假令牌作为他的IP地址!但是强迫吃盐很难,对吗?当然。但现代的GPU非常擅长。除非你使用足够的随机性(使它足够大),否则它会很快下降,并与它一起成为你城堡的钥匙。

简而言之,唯一能保护你的就是盐,它并不像你想象的那么保护你。

但是等等!

所有这些都是假设攻击者知道算法!如果这是秘密和混乱,那么你是安全的,对吗?错了。这种思维方式有一个名字:通过默默无闻的安全,这是不应该依赖的。

更好的方法

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

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

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

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

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

现在,为了展示这个数字有多大,让我们想象一下互联网上的每台服务器(比如今天的50000000)都试图以每秒1000000000的速度强制使用这个数字。实际上,在这样的负载下,您的服务器会融化,但让我们来解决这个问题。

1
2
3
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万亿次。太快了!对吗?

1
2
3
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秒…

让我们试着把它简化成更友好的数字。

1
215,626,585,489,599 years

或者更好:

1
47917 times the age of the universe

是的,那是宇宙年龄的47917倍…

基本上,它不会被破解。

综上所述:

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

1
2
3
4
5
6
7
8
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);
}

然后,要验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()。在这种情况下,可以用timingsafeccompare函数替换hash_equals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * 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;
}


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.

通常我会这样做:

  • 用户登录时使用"让我保持登录状态"
  • 创建会话
  • 创建一个名为something的cookie,其中包含:md5(salt+username+ip+salt)和一个名为somethinglese的cookie,其中包含id
  • 将cookie存储在数据库中
  • 用户做事情然后离开----
  • 用户返回,检查someThingelse cookie,如果存在,从该用户的数据库中获取旧哈希,检查cookie的内容与数据库中的哈希匹配,该哈希还应与新计算的哈希(对于IP)匹配,因此:cookie hash==databasehash==md5(salt+username+ip+salt),如果存在,转到2,如果不存在T转到1
  • 当然,您可以使用不同的cookie名称等。您也可以稍微更改cookie的内容,只需确保它不容易创建。例如,您还可以在创建用户时创建一个用户salt,并将其放入cookie中。

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


    介绍好的。

    您的标题"让我保持登录"-最佳方法使我很难知道从哪里开始,因为如果您正在寻找最佳方法,那么您必须考虑以下几点:好的。

    • 识别
    • 安全

    曲奇饼好的。

    cookie是易受攻击的,在常见的浏览器cookie盗窃漏洞和跨站点脚本攻击之间,我们必须接受cookie是不安全的。为了帮助提高安全性,您必须注意,phpsetcookies具有其他功能,例如好的。

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

    Ok.

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

    定义好的。

    • 令牌(不可预测的n长度随机字符串,如/dev/urandom)
    • 引用(不可预测的n长度随机字符串,例如/dev/urandom)
    • 签名(使用hmac方法生成键控哈希值)

    简单方法好的。

    一个简单的解决方案是:好的。

    • 用户使用"记住我"登录
    • 使用令牌和签名颁发的登录cookie
    • 返回时,检查签名
    • 如果签名可以……然后在数据库中查找用户名和令牌
    • 如果无效..返回登录页面
    • 如果有效,则自动登录

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

    • 没有办法知道饼干是不是被偷了
    • 攻击者可能是访问敏感操作,如更改密码或数据,如个人和烘焙信息等。
    • 受损的cookie对cookie的寿命仍然有效。

    更好的解决方案好的。

    更好的解决办法是好的。

    • 用户已登录并选择了"记住我"
    • 生成令牌签名并存储在cookie中
    • 令牌是随机的,仅对单个身份验证有效
    • 每次访问站点时都会更换令牌。
    • 当未登录的用户访问站点时,将验证签名、令牌和用户名。
    • 记住我登录应该有有限的访问权限,不允许修改密码、个人信息等。

    示例代码好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // 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
    "
    , $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
    "
    , $user);
        }
    } catch (Exception $e) {
        printf("#Error  %s
    "
    , $e->getMessage());
    }

    使用的类好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    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中测试好的。

    enter image description here。好的。

    优势好的。

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

    劣势好的。

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

    快速修复好的。

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

    多cookie方法好的。

    当攻击者要窃取cookie时,只能将其集中在特定的网站或域上,如example.com。好的。

    但实际上,您可以对来自两个不同域(example.com&fakeaddsite.com)的用户进行身份验证,使其看起来像"广告cookie"。好的。

    • 用户使用remember me登录到example.com
    • 在cookie中存储用户名、令牌和引用
    • 在数据库中存储用户名、令牌、引用,例如memcache
    • 通过get和iframe将reference id发送到fakeaddsite.com
    • fakeaddsite.com使用引用从数据库获取用户和令牌
    • fakeaddsite.com存储签名
    • 当用户从fakeaddsite.com返回带有iframe的fetch签名信息时
    • 合并IT数据并进行验证
    • …你知道剩下的

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

    enter image description here。好的。

    从上面的图片好的。

    • 当前访问的站点是本地主机
    • 它还包含从192.168.1.120设置的cookies

    192.168.1.120好的。

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

    优势好的。

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

    劣势好的。

    • 对服务器的多个请求仅用于一次登录

    改进好的。

    • 完成使用iframe使用ajax

    好啊。


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

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


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

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


    我将推荐Stefan所提到的方法(即遵循改进的持久登录cookie最佳实践中的指导原则),并建议您确保您的cookie是httponly cookie,因此它们不可被潜在恶意的javascript访问。


    旧线程,但仍然是一个有效的问题。我注意到一些关于安全性的很好的回应,并且避免使用"通过模糊来实现安全性",但是在我看来,给出的实际技术方法还不够。在我贡献我的方法之前我必须说的事情:好的。

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

    尽管如此,有两种很好的方法可以让您的系统自动登录。好的。

    首先,这是一种把一切都放在别人身上的廉价、简单的方法。如果你让你的网站支持用你的google+帐户登录,你可能有一个简化的google+按钮,如果用户已经登录google,你就可以登录(我在这里是为了回答这个问题,因为我总是登录google)。如果您希望用户在已经使用受信任和支持的验证器登录的情况下自动登录,并选中相应的复选框,请让客户端脚本在加载前执行相应的"使用登录"按钮后面的代码,只需确保服务器存储自动登录表中的唯一ID,该表具有用于用户的用户名、会话ID和验证器。由于这些登录方法使用Ajax,所以您无论如何都在等待响应,而该响应要么是经过验证的响应,要么是拒绝。如果您得到一个已验证的响应,请正常使用它,然后继续正常加载登录的用户。否则,登录失败,但不要告诉用户,只要继续不登录,他们就会注意到。这是为了防止窃取cookie(或伪造cookie以提升权限)的攻击者了解用户自动登录到站点。好的。

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

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

    • 用户"joe"第一次访问站点,会话ID放在cookie"session"中。
    • 用户"joe"登录、提升权限、获取新会话ID并更新cookie"session"。
    • 用户"joe"选择使用google+自动登录,获取cookie"keepmesignedin"中的唯一ID。
    • 用户"joe"让google保持他们的登录状态,允许你的网站在后端使用google自动登录用户。
    • 攻击者系统性地尝试"keepmesignedIn"的唯一ID(这是分发给每个用户的公共知识),但未登录到任何其他地方;尝试为"joe"提供唯一ID。
    • 服务器接收"joe"的唯一ID,将匹配的数据库拉入google+帐户。
    • 服务器将攻击者发送到登录页面,该页面向Google发送Ajax登录请求。
    • Google服务器接收请求,使用其API查看攻击者当前未登录。
    • Google发送的响应是,当前没有通过此连接登录的用户。
    • 攻击者的页面收到响应,脚本自动重定向到登录页面,并在URL中编码一个post值。
    • 登录页面获取post值,将"keepmesignedIn"的cookie发送到空值和有效期至1970年1月1日,以阻止自动尝试,从而导致攻击者的浏览器简单地删除cookie。
    • 攻击者得到了正常的第一次登录页面。

    不管怎样,即使攻击者使用不存在的ID,除了收到经过验证的响应外,所有尝试都应该失败。好的。

    对于使用外部验证器登录站点的用户,此方法可以也应该与内部验证器一起使用。好的。

    ===好的。

    现在,对于您自己的能够自动登录用户的验证器系统,我是这样做的:好的。

    DB有几个表:好的。

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

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

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

    请注意,此表中没有用户字段,因为登录时用户名在会话数据中,并且程序不允许空数据。可以使用随机MD5散列、sha1/128/256散列、添加随机字符串的日期时间戳(然后散列)或任何您想要的散列来生成会话u id和会话u令牌,但输出的熵应保持在可容忍的高度,以减轻甚至从地面上起飞时的暴力攻击,以及生成的所有散列。y在尝试添加会话类之前,应在会话表中检查它们是否匹配。好的。

    1
    2
    3
    4
    5
    6
    7
    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地址应该是唯一的,因此每个条目都有一个唯一的值是有意义的。另一方面,主机名可以合法地在单独的网络上复制。有多少人使用"家用电脑"作为他们的计算机名之一?用户名由服务器后端从会话数据中获取,因此无法对其进行操作。对于令牌,应使用为页面生成会话令牌的相同方法在cookie中生成令牌,以便用户自动登录。最后,为用户需要重新验证其凭证时添加日期时间代码。要么在用户登录时更新这个日期时间,将其保留在几天内,要么强制它过期,而不管最后一次登录是什么,只保留一个月左右,无论您的设计要求是什么。好的。

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

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

    一旦以这种方式登录,服务器仍应验证会话。在这里,您可以对被盗或损坏的系统进行编码;模式和其他预期的会话数据登录结果通常会导致系统被劫持或cookie被伪造以获得访问权的结论。在这里,ISS技术人员可以设置规则,以触发帐户锁定或自动从自动登录系统中删除用户,从而使攻击者能够保持足够长的时间,以便用户确定攻击者如何成功以及如何将其切断。好的。

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

    如果有人希望在我的答案中给出代码,我很抱歉,这在这里不会发生。我会说我使用php、jquery和ajax来运行我的站点,而且我从不使用Windows作为服务器…曾经。好的。好啊。


    生成一个哈希,可能只有你知道的秘密,然后将它存储在你的数据库中,这样它就可以与用户相关联。应该工作得很好。


    我读了所有的答案,仍然发现很难提取出我应该做的事情。如果一张图片值1K个字,我希望这能帮助其他人实现一个基于Barry Jaspan改进的持久登录cookie最佳实践的安全持久存储。

    enter image description here

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


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

    我想用这种方法来"记住我"。如果您看到任何问题,请发表评论。

  • 创建一个表,将"记住我"数据存储在-与用户表分开,以便我可以从多个设备登录。

  • 成功登录时(勾选"记住我"):

    a)生成一个唯一的随机字符串,用作此计算机上的用户ID:biguserid

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

    c)存储cookie:biguserid:bigkey

    d)在"Remember Me"表中,添加一条记录:userid、ip address、biguserid、bigkey

  • 如果试图访问需要登录的内容:

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

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

  • 注销时,将该用户的所有"记住我"记录标记为过期。

  • 我能看到的唯一弱点是;

    • 你可以拿到某人的笔记本电脑,然后用cookie伪造他们的IP地址。
    • 你每次都可以伪造一个不同的IP地址,并猜测整个事情——但是如果有两个大字符串匹配,那就是……做一个与上面类似的计算……我不知道……巨大的几率?


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

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

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

    加密$data,将type设置为httponly,并设置cookie。

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

  • 获取cookie数据。移除cookie中的危险字符。用:字符爆炸。
  • 检查有效性。如果cookie早于x天,则将用户重定向到登录页面。
  • 如果cookie不是旧的,从数据库获取最新的密码更改时间。如果在用户上次登录后更改了密码,请将用户重定向到登录页。
  • 如果pass最近没有更改,则获取用户的当前浏览器代理。检查(currentuseragenthash==cookieuseragenthash)。如果代理相同,请转到下一步,否则重定向到登录页。
  • 如果所有步骤成功通过,则授权用户名。
  • 如果用户退出,请删除此cookie。如果用户重新登录,则创建新的cookie。


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

    我能听到你问"为什么两天?为什么不两周?"。这是因为在PHP中使用会话会自动将到期时间推后。这是因为在PHP中会话的到期实际上是一个空闲超时。

    现在,我已经说过了,我可能会实现一个更困难的超时值,这个值存储在会话本身中,大约2周左右,然后添加代码来查看它,并强制使会话无效。或者至少注销它们。这意味着将要求用户定期登录。雅虎!做这个。