我的web应用程序使用会话存储关于用户的信息,一旦他们登录,并维护这些信息,因为他们在应用程序内从页面到页面。在这个特定的应用程序中,我存储的人的user_id, first_name和last_name。
我想在登录时提供一个“让我登录”选项,在用户的机器上放置一个cookie,为期两周,当他们返回应用程序时,将以相同的细节重新启动他们的会话。
做这件事的最佳方法是什么?我不想在cookie中存储他们的user_id,因为这似乎会让一个用户很容易尝试和伪造另一个用户的身份。
我的web应用程序使用会话存储关于用户的信息,一旦他们登录,并维护这些信息,因为他们在应用程序内从页面到页面。在这个特定的应用程序中,我存储的人的user_id, first_name和last_name。
我想在登录时提供一个“让我登录”选项,在用户的机器上放置一个cookie,为期两周,当他们返回应用程序时,将以相同的细节重新启动他们的会话。
做这件事的最佳方法是什么?我不想在cookie中存储他们的user_id,因为这似乎会让一个用户很容易尝试和伪造另一个用户的身份。
当前回答
我的解是这样的。它不是百分之百的防弹,但我认为它在大多数情况下都能帮你。
当用户成功登录时,创建一个包含以下信息的字符串:
$data = (SALT + ":" + hash(User Agent) + ":" + username
+ ":" + LoginTimestamp + ":"+ SALT)
加密$data,设置类型为HttpOnly,并设置cookie。
当用户回到你的网站时,执行以下步骤:
Get cookie data. Remove dangerous characters inside cookie. Explode it with : character. Check validity. If cookie is older than X days then redirect user to login page. 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. 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. If all steps passed successfully authorize username.
如果用户登出,请删除此cookie。如果用户重新登录,创建新的cookie。
其他回答
我在这里问了这个问题的一个角度,答案将引导您找到所需的所有基于令牌的超时cookie链接。
基本上,您不会将userId存储在cookie中。您存储了一个一次性令牌(巨大的字符串),用户使用它来拾取旧的登录会话。然后,为了使其真正安全,在进行重大操作(比如更改密码本身)时,需要输入密码。
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周左右退出,并添加代码来查看并强制使会话无效。或者至少让他们退出。这意味着将要求用户定期登录。雅虎这是否。
我读了所有的答案,仍然发现很难提取我应该做什么。如果一张图片相当于1k个单词,我希望这有助于其他人实现基于Barry Jaspan的改进的持久登录Cookie最佳实践的安全持久存储
如果您有问题、反馈或建议,我将尝试更新图表,以反映试图实现安全持久登录的新手。
简介
你的标题“让我登录”-最好的方法让我很难知道从哪里开始,因为如果你正在寻找最好的方法,那么你必须考虑以下几点:
识别 安全
饼干
在常见的浏览器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完成
我推荐Stefan提到的方法(即遵循改进的持久登录Cookie最佳实践中的指导方针),也建议你确保你的Cookie是HttpOnly Cookie,这样它们就不会被潜在的恶意JavaScript访问。