php “让我保持登录” - 最好的方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1354999/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
"Keep Me Logged In" - the best approach
提问by Matthew
My web application uses sessions to store information about the user once they've logged in, and to maintain that information as they travel from page to page within the app. In this specific application, I'm storing the user_id, first_nameand last_nameof the person.
我的 Web 应用程序使用会话来存储有关用户登录后的信息,并在他们在应用程序中从页面移动到页面时维护这些信息。在这个特定的应用程序中,我存储了人的user_id,first_name和last_name。
I'd like to offer a "Keep Me Logged In" option on log in that will put a cookie on the user's machine for two weeks, that will restart their session with the same details when they return to the app.
我想在登录时提供一个“让我保持登录”选项,它将在用户的机器上放置一个 cookie 两周,当他们返回应用程序时,它将使用相同的详细信息重新启动他们的会话。
What is the best approach for doing this? I don't want to store their user_idin the cookie, as it seems like that would make it easy for one user to try and forge the identity of another user.
这样做的最佳方法是什么?我不想将它们存储user_id在 cookie 中,因为这似乎会让一个用户很容易尝试伪造另一个用户的身份。
回答by ircmaxell
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.
好吧,让我坦率地说:如果您为此目的将用户数据或从用户数据派生的任何内容放入 cookie,那么您做错了。
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...
想象一下你是一个攻击者。您会在会话中看到为“记住我”设置的加密 cookie。它是 32 个字符宽。哎呀。那可能是一个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.
现在,攻击者需要做的就是暴力破解“盐”(这不是真正的盐,但稍后会详细介绍),他现在可以使用任何用户名作为其 IP 地址生成他想要的所有假令牌!但是强制加盐很难,对吧?绝对地。但是现代 GPU 非常擅长它。除非您在其中使用足够的随机性(使其足够大),否则它会迅速倒塌,并随之成为您城堡的钥匙。
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 NEVERbe relied upon.
所有这些都假设攻击者知道算法!如果它是秘密和令人困惑的,那么你是安全的,对吧?错了。这种思路有一个名字:安全性通过默默无闻,永远不应该依赖。
The Better Way
更好的方法
The better way is to never let a user's information leave the server, except for the id.
更好的方法是永远不要让用户的信息离开服务器,除了 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.
当用户登录时,生成一个大的(128 到 256 位)随机令牌。将其添加到将令牌映射到用户 ID 的数据库表中,然后将其发送到 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:
好吧,让我们在这里做一些数学运算。我们正在生成一个 128 位的随机令牌。这意味着有:
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.
现在,为了说明这个数字有多大,让我们想象一下互联网上的每台服务器(假设今天有 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
So 50 quadrillion guesses per second. That's fast! Right?
所以每秒 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
So 6.8 sextillion seconds...
所以 6.8 六亿秒...
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...
是的,那是宇宙年龄的 47917 倍……
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.
我推荐的更好的方法是将 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);
}
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 veryimportant that the SECRET_KEYbe a cryptographic secret (generated by something like /dev/urandomand/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 RandomLibor random_compat, or mcrypt_create_iv()with DEV_URANDOM)...
现在,这是一个密码秘密(由类似和/或源自高熵输入的东西生成)非常重要。此外,需要是一个强大的随机源(还不够强大。使用库,例如RandomLib或random_compat,或with )...SECRET_KEY/dev/urandomGenerateRandomToken()mt_rand()mcrypt_create_iv()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:
本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;
}
回答by Pim Jager
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 answerto this question for a more secure approach.
安全注意事项:将 cookie 基于确定性数据的 MD5 散列是一个坏主意;最好使用从 CSPRNG 派生的随机令牌。有关更安全的方法,请参阅ircmaxell对此问题的回答。
Usually I do something like this:
通常我会做这样的事情:
- User logs in with 'keep me logged in'
- Create session
- Create a cookie called SOMETHING containing: md5(salt+username+ip+salt) and a cookie called somethingElse containing id
- Store cookie in database
- User does stuff and leaves ----
- 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
- 用户使用“保持登录状态”登录
- 创建会话
- 创建一个名为 SOMETHING 的 cookie 包含:md5(salt+username+ip+salt) 和一个名为 somethingElse 的包含 id 的 cookie
- 将cookie存储在数据库中
- 用户做事然后离开----
- 用户返回,检查 somethingElse cookie,如果存在,从数据库中获取该用户的旧哈希,检查 cookie 的内容与数据库中的哈希匹配,该哈希也应与新计算的哈希匹配(对于ip) 因此:cookieHash==databaseHash==md5(salt+username+ip+salt),如果他们这样做,转到2,如果他们不转到1
Off 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.
当然,您可以使用不同的 cookie 名称等。您也可以稍微更改 cookie 的内容,只要确保它不容易创建即可。例如,您还可以在创建用户时创建一个 user_salt,并将其放入 cookie。
Also you could use sha1 instead of md5 (or pretty much any algorithm)
你也可以使用 sha1 而不是 md5 (或几乎任何算法)
回答by Baba
Introduction
介绍
Your title “Keep Me Logged In” - the best approachmake 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 phpsetcookieshas additional functionality such as
Cookie 是脆弱的,在常见的浏览器 cookie 窃取漏洞和跨站点脚本攻击之间,我们必须承认 cookie 是不安全的。为了帮助提高安全性,您必须注意phpsetcookies具有附加功能,例如
bool setcookie( string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, bool $secure= false [, bool $httponly= false ]]]]]] )
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)
- 安全(使用 HTTPS 连接)
- httponly(通过 XSS 攻击减少身份盗用)
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)
- 令牌(不可预测的 n 长度随机字符串,例如 /dev/urandom)
- 参考(不可预测的 n 长度随机字符串,例如 /dev/urandom)
- 签名(使用 HMAC 方法生成密钥散列值)
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
- 用户使用“记住我”登录
- 使用令牌和签名发布的登录 Cookie
- 返回时,检查签名
- 如果签名没问题..然后在数据库中查找用户名和令牌
- 如果无效..返回登录页面
- 如果有效自动登录
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
- 无法知道 cookie 是否被盗
- 攻击者可能会访问敏感操作,例如更改密码或个人和烘焙信息等数据。
- 被破坏的 cookie 在 cookie 的生命周期内仍然有效
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.
- 用户已登录并选择记住我
- 生成令牌和签名并存储在 cookie 中
- 令牌是随机的,仅对单次认证有效
- 每次访问网站时都会替换令牌
- 当未登录的用户访问该站点时,签名、令牌和用户名将被验证
- 记住我登录应具有有限的访问权限,不允许修改密码、个人信息等。
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 HiHymaned 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
在 Firefox 和 Chrome 中测试


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
- 更好的安全性
- 攻击者访问受限
- 当 cookie 被盗时,它只对单次访问有效
- 当原始用户下次访问该站点时,您可以自动检测并通知用户被盗
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.
- 不支持通过多个浏览器的持久连接(移动和网络)
- cookie 仍然可以被窃取,因为用户只有在下次登录后才会收到通知。
Quick Fix
快速解决
- Introduction of approval system for each system that must have persistent connection
- Use multiple cookies for the authentication
- 为每个必须长连接的系统引入审批系统
- 使用多个 cookie 进行身份验证
Multiple Cookie Approach
多个 Cookie 方法
When an attacker is about to steal cookies the only focus it on a particular website or domain eg. example.com
当攻击者即将窃取 cookie 时,只能将其集中在特定网站或域上,例如。例子.com
But really you can authenticate a user from 2 different domains (example.com& fakeaddsite.com) and make it look like "Advert Cookie"
但实际上,您可以对来自 2 个不同域(example.com和fakeaddsite.com)的用户进行身份验证,并使其看起来像“广告 Cookie”
- User Logged on to example.comwith 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
- 用户使用记住我登录到example.com
- 将用户名、令牌、引用存储在 cookie 中
- 将用户名、令牌、引用存储在数据库中,例如。内存缓存
- 通过 get 和 iframe 将引用ID 发送到fakeaddsite.com
- fakeaddsite.com 使用引用从数据库中获取用户和令牌
- fakeaddsite.com 存储签名
- 当用户从 fakeaddsite.com 返回使用 iframe 获取签名信息时
- 合并数据并进行验证
- ......你知道剩下的
Some people might wonder how can you use 2 different cookies ? Well its possible, imagine example.com = localhostand fakeaddsite.com = 192.168.1.120. If you inspect the cookies it would look like this
有些人可能想知道如何使用 2 个不同的 cookie?那么它可能,想象example.com = localhost和fakeaddsite.com = 192.168.1.120。如果您检查 cookie,它看起来像这样


From the image above
从上图
- The current site visited is localhost
- It also contains cookies set from 192.168.1.120
- 当前访问的站点是本地主机
- 它还包含从 192.168.1.120 设置的 cookie
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
- 只接受定义
HTTP_REFERER - 只接受来自指定的连接
REMOTE_ADDR - 没有 JavaScript,没有内容,但除了签署信息并从 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
- 99% 的时间你都欺骗了攻击者
- 您可以在攻击者第一次尝试时轻松锁定帐户
- 与其他方法一样,即使在下次登录之前也可以防止攻击
Disadvantage
坏处
- Multiple Request to server just for a single login
- 多次请求服务器仅用于单次登录
Improvement
改进
- Done use iframe use
ajax
- 完成使用 iframe 使用
ajax
回答by Dan Rosenstark
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.
我在这里问了这个问题的一个角度,答案将引导您找到您需要的所有基于令牌的超时 cookie 链接。
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).
基本上,您不会将 userId 存储在 cookie 中。您存储一个一次性令牌(巨大的字符串),用户用它来获取旧的登录会话。然后,为了使其真正安全,您需要为繁重的操作(例如更改密码本身)提供密码。
回答by user253780
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:
旧线程,但仍然是一个有效的问题。我注意到一些关于安全性的良好反应,并避免使用“通过默默无闻的安全性”,但给出的实际技术方法在我看来还不够。在我贡献我的方法之前我必须说的事情:
- NEVERstore a password in clear text...EVER!
- NEVERstore 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 evershare 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.
- 永远不要以明文形式存储密码......永远!
- 切勿将用户的散列密码存储在数据库中的多个位置。您的服务器后端始终能够从用户表中提取散列密码。存储冗余数据代替额外的 DB 事务并不是更有效,反之亦然。
- 您的会话ID的应该是唯一的,所以没有两个用户可以永远共享一个ID,一个ID的,因此目的(可能你的驾驶执照身号码永远匹配另一个人?号)这会产生一个两件独特的组合,基于2独特的字符串。您的会话表应该使用 ID 作为 PK。为了允许多个设备被信任用于自动登录,请使用另一个包含所有已验证设备列表的受信任设备表(请参见下面的示例),并使用用户名进行映射。
- 将已知数据散列到 cookie 中没有任何意义,cookie 可以被复制。我们正在寻找的是一种合规的用户设备,以提供在攻击者不损害用户机器的情况下无法获得的真实信息(再次参见我的示例)。然而,这意味着禁止其机器的静态信息(即 MAC 地址、设备主机名、如果受浏览器限制的用户代理等)保持一致(或首先欺骗它)的合法用户将无法使用此功能。但如果这是一个问题,请考虑这样一个事实,即您正在向唯一标识自己的用户提供自动登录,所以如果他们通过欺骗他们的 MAC、欺骗他们的用户代理、欺骗/更改他们的主机名、隐藏在代理后面等方式拒绝被认识,那么他们就无法识别,并且永远不应该为自动服务进行身份验证。如果你想要这个,你需要研究与客户端软件捆绑在一起的智能卡访问,为正在使用的设备建立身份。
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.
首先,廉价、简单的方法把一切都放在别人身上。如果你让你的网站支持登录,比如你的 google+ 帐户,你可能有一个简化的 google+ 按钮,如果用户已经登录谷歌,它会登录用户(我在这里回答这个问题,因为我总是登录谷歌)。如果您希望用户在用户已使用受信任且受支持的身份验证器登录时自动登录,并选中该框以执行此操作,请让您的客户端脚本在加载之前执行相应的“登录方式”按钮后面的代码,只需确保服务器在具有用户名、会话 ID 和用于用户的身份验证器的自动登录表中存储唯一 ID。由于这些登录方法使用 AJAX,因此您无论如何都在等待响应,该响应要么是经过验证的响应,要么是拒绝。如果您收到经过验证的响应,请照常使用它,然后继续照常加载登录用户。否则,登录失败,但不要告诉用户,只是继续作为未登录,他们会注意到。这是为了防止窃取 cookie(或伪造 cookie 以尝试提升权限)的攻击者获悉用户自动登录站点。
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.
这很便宜,并且也可能被某些人认为是肮脏的,因为它会尝试验证您可能已经在 Google 和 Facebook 等地方登录的自我,甚至没有告诉您。但是,它不应该用于没有要求自动登录您的网站的用户,并且这种特殊方法仅用于外部身份验证,例如 Google+ 或 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:
由于外部身份验证器用于在后台告诉服务器用户是否经过验证,因此攻击者除了唯一 ID 之外无法获得任何其他信息,而这本身是无用的。我会详细说明:
- 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.
- 用户 'joe' 第一次访问站点,会话 ID 放置在 cookie 'session' 中。
- 用户 'joe' 登录,提升权限,获取新的会话 ID 并更新 cookie '会话'。
- 用户 'joe' 选择使用 google+ 自动登录,并在 cookie 'keepmesignedin' 中获得一个唯一 ID。
- 用户“joe”让谷歌让他们保持登录状态,允许您的网站在后端使用谷歌自动登录用户。
- 攻击者系统地尝试使用唯一的“keepmesignedin”ID(这是分发给每个用户的公共知识),并且不会登录到其他任何地方;尝试给 'joe' 的唯一 ID。
- 服务器收到“joe”的唯一 ID,在数据库中为 google+ 帐户提取匹配项。
- 服务器将攻击者发送到运行 AJAX 请求到谷歌登录的登录页面。
- Google 服务器收到请求,使用其 API 来查看 Attacker 当前未登录。
- Google 发送响应,指出当前没有通过此连接登录的用户。
- 攻击者的页面收到响应,脚本自动重定向到登录页面,在 url 中编码 POST 值。
- 登录页面获取 POST 值,将 'keepmesignedin' 的 cookie 发送到一个空值和 1-1-1970 的有效截止日期以阻止自动尝试,从而导致攻击者的浏览器简单地删除 cookie。
- 攻击者获得正常的首次登录页面。
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.
无论如何,即使攻击者使用了不存在的 ID,除非收到经过验证的响应,否则所有尝试都应该失败。
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:
DB有几个表:
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.
请注意,用户名的长度可以为 255 个字符。我的服务器程序将系统中的用户名限制为 32 个字符,但外部身份验证器的用户名可能比其 @domain.tld 大,因此我只支持电子邮件地址的最大长度以实现最大兼容性。
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.
注意这个表中没有用户字段,因为用户名在登录时是在会话数据中的,程序不允许空数据。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 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.
MAC 地址本质上应该是唯一的,因此每个条目都有一个唯一的值是有道理的。另一方面,主机名可以合法地在不同的网络上复制。有多少人使用“Home-PC”作为他们的计算机名称之一?用户名是由服务器后端从会话数据中获取的,因此无法对其进行操作。至于令牌,应该使用为页面生成会话令牌的相同方法在 cookie 中为用户自动登录生成令牌。最后,当用户需要重新验证其凭据时,会添加日期时间代码。要么在用户登录时更新此日期时间,将其保留在几天内,要么强制它过期,无论上次登录如何,只保留一个月左右,无论您的设计如何。
This prevents someone from systematically spoofing the MAC and hostname for a user they know auto-signs in. NEVERhave 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.
这可以防止有人系统地欺骗他们知道自动登录的用户的 MAC 和主机名。永远不要让用户使用他们的密码、明文或其他方式保存 cookie。在每个页面导航上重新生成令牌,就像会话令牌一样。这大大降低了攻击者获取有效令牌 cookie 并使用它登录的可能性。有些人会试图说攻击者可以从受害者那里窃取 cookie 并进行会话重放攻击以登录。如果攻击者可以窃取 cookie(这是可能的),他们肯定会破坏整个设备,这意味着他们无论如何都可以使用设备登录,这完全违背了窃取 cookie 的目的。只要您的站点通过 HTTPS 运行(在处理密码、CC 号码或其他登录系统时应该这样做),您就已经为用户提供了在浏览器中所能提供的所有保护。
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.
要记住的一件事:如果您使用自动登录,会话数据不应过期。您可以错误地使继续会话的能力失效,但如果会话数据是预期在会话之间继续的持久数据,则在系统中验证应该恢复会话数据。如果您想要持久和非持久会话数据,请使用另一个表来存储以用户名作为 PK 的持久会话数据,并让服务器像正常会话数据一样检索它,只需使用另一个变量。
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 hiHymaned 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.
以这种方式登录后,服务器仍应验证会话。在这里,您可以为被盗或受感染的系统编写预期代码;登录会话数据的模式和其他预期结果通常会导致系统被劫持或伪造 cookie 以获取访问权限的结论。在这里,您的 ISS 技术人员可以设置规则,触发帐户锁定或从自动登录系统中自动删除用户,将攻击者拒之门外的时间足够长,以便用户确定攻击者如何成功以及如何将其切断。
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.
如果有人希望在我的回答中给出代码,我深表歉意,这不会发生在这里。我会说我使用 PHP、jQuery 和 AJAX 来运行我的网站,而且我从不使用 Windows 作为服务器......永远。
回答by Walter Rumsby
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 cookiesso they are not accessible to, potentially malicious, JavaScript.
我会推荐 Stefan 提到的方法(即遵循改进的持久登录 Cookie 最佳实践中的指南),并建议您确保您的 cookie 是HttpOnly cookie,这样它们就不会被潜在的恶意 JavaScript 访问。
回答by Jani Hartikainen
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.
生成一个散列,可能只有您知道的秘密,然后将其存储在您的数据库中,以便它可以与用户相关联。应该工作得很好。
回答by trante
My solution is like this. It's not 100% bulletproof but I think it will save you for the most of the cases.
我的解决方案是这样的。它不是 100% 防弹,但我认为它会在大多数情况下为您节省。
When user logged in successfully create a string with this information:
当用户成功登录时,使用以下信息创建一个字符串:
$data = (SALT + ":" + hash(User Agent) + ":" + username
+ ":" + LoginTimestamp + ":"+ SALT)
Encrypt $data, set type to HttpOnlyand set cookie.
加密$data,将 type 设置为HttpOnly并设置 cookie。
When user come back to your site, Make this steps:
当用户回到您的网站时,请执行以下步骤:
- 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 超过 X 天,则将用户重定向到登录页面。
- 如果cookie不是旧的;从数据库获取最新的密码更改时间。如果在用户上次登录后更改密码,则将用户重定向到登录页面。
- 如果最近没有更改通行证;获取用户当前的浏览器代理。检查是否 (currentUserAgentHash == cookieUserAgentHash)。如果代理相同,则转到下一步,否则重定向到登录页面。
- 如果所有步骤都成功通过,则授权用户名。
If user signouts, remove this cookie. Create new cookie if user re-logins.
如果用户退出,请删除此 cookie。如果用户重新登录,则创建新的 cookie。
回答by Enigma Plus
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.
我不明白将加密内容存储在 cookie 中的概念,因为它是您需要进行黑客攻击的加密版本。如果我遗漏了什么,请发表评论。
I am thinking about taking this approach to 'Remember Me'. If you can see any issues, please comment.
我正在考虑采用这种方法来“记住我”。如果您看到任何问题,请发表评论。
Create a table to store "Remember Me" data in - separate to the user table so that I can log in from multiple devices.
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
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.
On logout, Mark all the "Remember Me" records for that user as expired.
创建一个表来存储“记住我”数据 - 与用户表分开,以便我可以从多个设备登录。
成功登录后(勾选记住我):
a) 生成一个唯一的随机字符串作为本机的 UserID:bigUserID
b) 生成唯一的随机字符串:bigKey
c) 存储一个 cookie:bigUserID:bigKey
d) 在“记住我”表中,添加一条记录:用户 ID、IP 地址、bigUserID、bigKey
如果尝试访问需要登录的内容:
a) 检查 cookie 并使用匹配的 IP 地址搜索 bigUserID 和 bigKey
b) 如果找到,请登录该人,但在用户表中设置“软登录”标志,以便进行任何危险操作,提示完全登录。
注销时,将该用户的所有“记住我”记录标记为已过期。
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?
- 你可以拿到某人的笔记本电脑并用 cookie 欺骗他们的 IP 地址。
- 你可以每次欺骗一个不同的 IP 地址并猜测整个事情 - 但是有两个大字符串要匹配,那就是......做一个与上面类似的计算......我不知道......很大的几率?
回答by Josh Woodcock
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
我阅读了所有答案,但仍然发现很难提取出我应该做的事情。如果一张图片值一千字,我希望这有助于其他人实现基于 Barry Jaspan改进的持久登录 Cookie 最佳实践的安全持久存储
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.
如果您有问题、反馈或建议,我将尝试更新图表以反映尝试实施安全持久登录的新手。

