我的web应用程序使用会话存储关于用户的信息,一旦他们登录,并维护这些信息,因为他们在应用程序内从页面到页面。在这个特定的应用程序中,我存储的人的user_id, first_name和last_name。
我想在登录时提供一个“让我登录”选项,在用户的机器上放置一个cookie,为期两周,当他们返回应用程序时,将以相同的细节重新启动他们的会话。
做这件事的最佳方法是什么?我不想在cookie中存储他们的user_id,因为这似乎会让一个用户很容易尝试和伪造另一个用户的身份。
我的web应用程序使用会话存储关于用户的信息,一旦他们登录,并维护这些信息,因为他们在应用程序内从页面到页面。在这个特定的应用程序中,我存储的人的user_id, first_name和last_name。
我想在登录时提供一个“让我登录”选项,在用户的机器上放置一个cookie,为期两周,当他们返回应用程序时,将以相同的细节重新启动他们的会话。
做这件事的最佳方法是什么?我不想在cookie中存储他们的user_id,因为这似乎会让一个用户很容易尝试和伪造另一个用户的身份。
当前回答
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.
我能听到你在问“为什么是2天?”为什么不是两周?”这是因为在PHP中使用会话会自动将过期时间向后推。这是因为在PHP中会话的过期实际上是一个空闲超时。
现在,我可能会实现一个更难的超时值,我将其存储在会话本身中,并在2周左右退出,并添加代码来查看并强制使会话无效。或者至少让他们退出。这意味着将要求用户定期登录。雅虎这是否。
其他回答
好吧,让我直截了当地说:如果你为了这个目的把用户数据,或者任何从用户数据衍生出来的东西放到cookie里,你就做错了。
在那里。我说了。现在我们来看看真正的答案。
你可能会问,哈希用户数据有什么问题?好吧,这归结于曝光表面和通过隐藏来保证安全。
想象一下,你是一个攻击者。您将看到为会话上的remember-me设置的加密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
所以每秒要猜5千万亿次。这就是快!对吧?
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的10的10次方秒…
让我们试着把它降低到更友好的数字。
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以下的版本,则不支持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;
}
我推荐Stefan提到的方法(即遵循改进的持久登录Cookie最佳实践中的指导方针),也建议你确保你的Cookie是HttpOnly Cookie,这样它们就不会被潜在的恶意JavaScript访问。
生成一个散列,其中可能包含只有您知道的秘密,然后将其存储在您的DB中,以便与用户关联。应该工作得很好。
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.
我能听到你在问“为什么是2天?”为什么不是两周?”这是因为在PHP中使用会话会自动将过期时间向后推。这是因为在PHP中会话的过期实际上是一个空闲超时。
现在,我可能会实现一个更难的超时值,我将其存储在会话本身中,并在2周左右退出,并添加代码来查看并强制使会话无效。或者至少让他们退出。这意味着将要求用户定期登录。雅虎这是否。
简介
你的标题“让我登录”-最好的方法让我很难知道从哪里开始,因为如果你正在寻找最好的方法,那么你必须考虑以下几点:
识别 安全
饼干
在常见的浏览器cookie盗窃漏洞和跨站点脚本攻击之间,我们必须接受cookie是不安全的。为了帮助提高安全性,您必须注意php setcookies具有额外的功能,例如
Bool setcookie (string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, Bool $secure = false [, Bool $httponly = false]]]]]])
secure(使用HTTPS连接) httponly(通过XSS攻击减少身份盗窃)
定义
令牌(长度为n的不可预知的随机字符串,例如。/dev/urandom) 参考(长度为n的不可预知的随机字符串,例如。/dev/urandom) 签名(使用HMAC方法生成键控哈希值)
简单的方法
一个简单的解决方案是:
用户使用“记住我”登录 带令牌和签名的登录Cookie 什么时候返回,检查签名 如果签名是ok ..然后在数据库中查找username & token 如果无效..返回登录页面 如果有效,自动登录
上面的案例研究总结了本页上所有的例子,但它们的缺点是
没有办法知道饼干是不是被偷了 攻击者可能是访问敏感操作,如更改密码或数据,如个人和烘焙信息等。 受损害的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.com & fakeaddsite.com)验证用户,并使其看起来像“广告Cookie”
用户使用“记住我”登录example.com 存储用户名,令牌,在cookie引用 存储用户名,令牌,在数据库引用等。Memcache 通过get和iframe发送引用id到fakeaddsite.com fakeaddsite.com使用引用从数据库获取用户和令牌 Fakeaddsite.com存储签名 当用户从fakeaddsite.com返回带有iframe的签名信息时 结合数据并进行验证 …你知道剩下的
有些人可能会想,你怎么能使用两个不同的cookie呢?这是可能的,想象example.com = localhost和fakeaddsite.com = 192.168.1.120。如果你检查饼干,它看起来是这样的
从上图来看
当前访问的站点是localhost 它还包含从192.168.1.120设置的cookie
192.168.1.120
只接受定义的HTTP_REFERER 只接受来自指定REMOTE_ADDR的连接 没有JavaScript,没有内容,但除了签名信息和从cookie中添加或检索它之外没有任何内容
优势
99%的情况下你都能骗过攻击者 您可以在攻击者第一次尝试时轻松锁定该帐户 与其他方法一样,在下次登录之前就可以阻止攻击
缺点
多个请求到服务器,只是为了一个登录
改进
使用iframe使用ajax完成