当前位置:  开发笔记 > 编程语言 > 正文

"让我登录" - 最好的方法

如何解决《"让我登录"-最好的方法》经验,为你挑选了6个好方法。

我的Web应用程序使用会话在用户登录后存储有关用户的信息,并在应用程序中从一个页面移动到另一个页面时维护该信息.在这个特定的应用程序,我存储user_id,first_namelast_name人的.

我想在登录时提供"Keep Me Logged In"选项,这将在用户的计算机上放置一个cookie两周,这将在他们返回应用程序时以相同的细节重新启动他们的会话.

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



1> ircmaxell..:

好吧,让我直言不讳地说:如果你为了这个目的将用户数据或从用户数据派生的任何内容放入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()是远远不够强,使用图书馆,如RandomLib或random_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;
}


这很奇怪,因为你的代码与你的答案相矛盾.你说"如果你把用户数据放入一个cookie [...]你做错了什么",但这正是你的代码正在做的事情!是不是更好的从cookie中删除用户名,仅计算令牌上的哈希值(并且可能添加ip地址以防止cookie被盗)然后在rememberMe()中执行fetchUsernameByToken而不是fetchTokenByUserName?
自PHP 5.6起,[hash_equals](http://php.net/manual/en/function.hash-equals.php)可用于在进行字符串比较时防止计时攻击.
但这种方法是不是意味着任何人都可以使用此用户名和cookie并以任何其他设备的身份登录此用户?
lol :-),请注意47917年是猜测的最长时间,随机令牌也可以在1小时内猜到.
@Levit它可以阻止某人获取有效令牌,并更改附加到其上的用户ID.
很多东西在这个答案中,但是正如@levit注意到的那样,cookie中的散列是不必要的,因为用户和令牌都存储在数据库中.仅更改cookie中的用户ID将无法帮助攻击者.数据库中的令牌[需要进行哈希处理](http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/)(它不在此处)以防止在数据库被盗时被滥用.fetchTokenByUserName是一个坏主意,因为你可以从多个电脑,平板电脑,智能手机登录.最好检查令牌和用户名的组合是否在数据库中.
我非常喜欢这个答案,这说:hmac如何在这个例子中有所作为.如果其中还有其他未知信息(时间,浏览器信息......),那将是有道理的,但就目前而言,它似乎没有任何区别.如果cookie被盗,那么有或没有hmac它将被接受.如果没有,那么在没有它的情况下,它应该平均需要47917/2个宇宙的生命周期来猜测它(好吧,加上一些安全性,通过`SECRET_KEY`添加的默默无闻;-)).或者我在这里遗漏了什么?
@Arken不,SECRET_KEY必须在代码中保持安全.如果您的数据库被黑客攻击而不是您的代码,那么您肯定会遇到麻烦,但至少您的秘密是安全的.
好的,所以如果秘密留在代码中,它是每次用户加载页面时随机生成的,还是一个永不改变的常量变量?
@storm_buster可能会在一小时内猜到.(此外,它是宇宙的47917年龄,而不是年.)然而,在宇宙的年龄中猜测它的概率是/ 47917.期望值是47917/2,这似乎是安全的.:-)
据我所知,cookie可能只是一个没有加密的用户ID。好的-很难估计的用户ID,但是您存储的正是您需要登录的内容。我是否缺少某些内容?
抱歉,为什么在验证功能中间返回false?
无论解决方案如何,如果攻击者A直接从受害者V的计算机上窃取cookie,我们就无能为力了,对吗?A可以登录,因为他是V,没有密码.至少直到下次V登录(如果你每次更改cookie).IP检查不是真正的解决方案,因为人们经常更改IP.有人可以说,如果A可以访问V的机器,那时他可以做任何他想做的事情; 事实并非如此,他可以在很短的时间内(足以偷走饼干)进入它,然后用他的机器完成其余的"工作".

2> Pim Jager..:

安全注意事项:将cookie从确定性数据的MD5哈希中删除是一个坏主意; 最好使用从CSPRNG派生的随机令牌.有关更安全的方法,请参阅ircmaxell对此问题的回答.

通常我做这样的事情:

    用户使用"让我登录"登录

    创建会话

    创建一个名为SOMETHING的cookie,其中包含:md5(salt + username + ip + salt)和一个名为somethingElse的cookie,其中包含id

    将cookie存储在数据库中

    用户做事和离开----

    用户返回,检查somethingElse cookie,如果存在,则从数据库中获取该用户的旧哈希,检查cookie SOMETHING与数据库中的哈希匹配的内容,该哈希值也应与新计算的哈希值匹配(对于ip)因此:cookieHash == databaseHash == md5(salt + username + ip + salt),如果他们这样做,转到2,如果他们不转到1

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

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


为什么在哈希中包含IP?此外,请确保在Cookie中包含时间戳信息,并使用此信息确定Cookie的最大年龄,以便您不会创建有益于永恒的身份令牌.
@Abhishek Dilliwal:这是一个非常古老的线索,但我遇到它寻找与Mathew相同的答案.我不认为使用session_ID会对Pim的答案起作用,因为你无法检查db hash,cookie hash和当前的session_ID,因为session_ID每次session_start()都会改变; 我以为我会指出这一点.
令牌应该是RANDOM,不以任何方式与用户/他的IP /他的useragent /任何东西连接.这是主要的安全漏洞.
你为什么用两种盐?MD5(盐+使用者名称+ IP +盐)
我很抱歉是沉闷但是第二个饼干的目的是什么?在这种情况下,id是什么?它只是一个简单的"真/假"值来表明用户是否想要使用"保持登录"功能?如果是这样,为什么不直接检查cookie SOMETHING是否存在?如果用户不希望他们的登录持续存在,那么SOMETHING cookie就不会出现在那里吗?最后,您是否再次动态生成哈希并将其作为额外的安全措施来检查cookie和数据库?
@Pim Jager:我也很好奇为什么你需要包含IP - 你的解决方案主要集中在不希望分配不同IP的桌面Web应用程序上,或者如果应用程序是打算用于各种网络的移动/笔记本电脑设备?

3> Baba..:

介绍

您的标题"让我登录" - 最好的方法让我很难知道从哪里开始,因为如果您正在寻找最佳方法,那么您将不得不考虑以下事项:

鉴定

安全

饼干

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方法生成键控哈希值)

简单的方法

一个简单的解决方案是:

用户使用"记住我"登录

登录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和Chrome中测试

在此输入图像描述

优点

更好的安全

攻击者访问受限

当cookie被盗时,它仅对单一访问有效

当下一个原始用户访问该站点时,您可以自动检测并通知用户盗窃

坏处

不支持通过多个浏览器(移动和Web)进行持久连接

cookie仍然可以被盗,因为用户只能在下次登录后收到通知.

快速解决

为必须具有持久连接的每个系统引入审批系统

使用多个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.如果您检查cookie,它将看起来像这样

在此输入图像描述

从上面的图像

访问的当前站点是localhost

它还包含从192.168.1.120设置的cookie

192.168.1.120

只接受定义 HTTP_REFERER

仅接受来自指定的连接 REMOTE_ADDR

没有JavaScript,没有内容,只包括签名信息,并从cookie添加或检索它

优点

99%的时间你欺骗了攻击者

您可以轻松锁定攻击者首次尝试的帐户

与其他方法一样,即使在下次登录之前也可以防止攻击

坏处

多个请求服务器只需一次登录

起色

完成使用iframe使用 ajax


虽然@ircmaxell很好地描述了这个理论,但我更喜欢这种方法,因为它工作得很好而不需要存储用户ID(这将是一个不需要的公开),并且还包括更多的指纹,而不仅仅是用户ID和哈希来识别用户,例如浏览器.这使得攻击者更难以使用被盗的cookie.这是迄今为止我见过的最好,最安全的方法.+1

4> Stefan Gehri..:

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

持久登录Cookie最佳实践

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



5> Dan Rosensta..:

我在这里询问了这个问题的一个角度,答案将引导您获得所需的所有基于令牌的超时cookie链接.

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



6> Walter Rumsb..:

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

推荐阅读
无名有名我无名_593
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有