Система входа в PHP: Запомнить меня (постоянный файл cookie)

Я хотел бы добавить опцию «запомнить меня» перед входом в систему.

Как лучше всего безопасно хранить файл cookie в браузере пользователя?

Например, в Facebook есть флажок «запомнить меня», поэтому каждый раз, когда вы заходите на facebook.com, вы уже входите в систему.

В моем текущем входе в систему используются простые сеансы.


person Karem    schedule 27.06.2010    source источник
comment
Вы можете ознакомиться с github.com/delight-im/PHP-Auth и его источник, чтобы узнать, как реализовать безопасную функцию "запомнить меня". По сути, просто сохраните некоторую очень длинную (т.е. с большой энтропией) строку случайных данных в файле cookie. Когда пользователь посещает вашу страницу, проверьте этот токен в своей базе данных, в которой вы отслеживаете эти токены. Если токен действителен, аутентифицируйте пользователя.   -  person caw    schedule 13.07.2016


Ответы (1)


Обновление (13 августа 2017 г.): чтобы понять, почему мы разделяем selector и token, а не просто используем token, прочтите эта статья о разделении токенов для предотвращения временных атак на запросы SELECT.

Я собираюсь извлечь стратегию, изложенную в этом сообщении блога о безопасной долгосрочной аутентификации, поскольку это охватывает множество вопросов, и нас интересует только " запомни меня ".

Преамбула - структура базы данных

Нам нужна отдельная таблица из таблицы наших пользователей, которая выглядит так (MySQL):

CREATE TABLE `auth_tokens` (
    `id` integer(11) not null UNSIGNED AUTO_INCREMENT,
    `selector` char(12),
    `token` char(64),
    `userid` integer(11) not null UNSIGNED,
    `expires` datetime,
    PRIMARY KEY (`id`)
);

Здесь важно то, что selector и token - это отдельные поля.

После входа в систему

Если у вас нет random_bytes(), просто возьмите копию random_compat.

if ($login->success && $login->rememberMe) { // However you implement it
    $selector = base64_encode(random_bytes(9));
    $authenticator = random_bytes(33);

    setcookie(
        'remember',
         $selector.':'.base64_encode($authenticator),
         time() + 864000,
         '/',
         'yourdomain.com',
         true, // TLS-only
         true  // http-only
    );

    $database->exec(
        "INSERT INTO auth_tokens (selector, token, userid, expires) VALUES (?, ?, ?, ?)", 
        [
            $selector,
            hash('sha256', $authenticator),
            $login->userId,
            date('Y-m-d\TH:i:s', time() + 864000)
        ]
    );
}

Повторная аутентификация при загрузке страницы

if (empty($_SESSION['userid']) && !empty($_COOKIE['remember'])) {
    list($selector, $authenticator) = explode(':', $_COOKIE['remember']);

    $row = $database->selectRow(
        "SELECT * FROM auth_tokens WHERE selector = ?",
        [
            $selector
        ]
    );

    if (hash_equals($row['token'], hash('sha256', base64_decode($authenticator)))) {
        $_SESSION['userid'] = $row['userid'];
        // Then regenerate login token as above
    }
}

Подробности

Мы используем 9 байтов случайных данных (в кодировке base64 до 12 символов) для нашего селектора. Это обеспечивает 72 бита пространства ключей и, следовательно, 2 36 битов устойчивости к столкновениям (атаки дня рождения), что в 16 раз больше, чем емкость нашего хранилища (integer(11) UNSIGNED).

Мы используем 33 байта (264 бита) случайности для нашего фактического аутентификатора. Это должно быть непредсказуемо во всех практических сценариях.

Мы храним SHA256-хэш аутентификатора в базе данных. Это снижает риск олицетворения пользователя после утечки информации.

Мы повторно вычисляем хэш SHA256 значения аутентификатора, хранящегося в файле cookie пользователя, затем сравниваем его с сохраненным хешем SHA256, используя _11 _ Для предотвращения тайминговых атак.

Мы отделили селектор от аутентификатора, потому что поиск в БД не является постоянным по времени. Это устраняет потенциальное влияние утечек времени на поиск, не вызывая резкого снижения производительности.

person Scott Arciszewski    schedule 09.05.2015
comment
Превосходная работа. Стоит ли добавлять в код проверку истечения срока действия? Вы регенерируете токен в другом месте вашего кода (например, загружается 1 из 10 страниц)? - person rybo111; 13.06.2015
comment
Ага! Выглядит отлично и все такое, но я не могу заставить hash_equals вернуть истину с помощью любой из этих пользовательских функций: php.net/hash_equals # 115664 - person rybo111; 13.06.2015
comment
Если вам нужна реализация на PHP: github.com/sarciszewski/php-future - person Scott Arciszewski; 14.06.2015
comment
Вы регенерируете жетон после того, как он был использован один раз. - person Scott Arciszewski; 14.06.2015
comment
Что, если пользователь поставил галочку «запомнить меня» и оставил свой сеанс открытым на время? Вы регенерируете его в какой-либо момент в течение одного сеанса? - person rybo111; 14.06.2015
comment
Нет, помните, что я должен повторно аутентифицировать их по истечении срока их сеанса (то есть, когда вы завтра снова включите свой компьютер). Краткосрочная аутентификация сеанса должна быть просто идентификатором сеанса (т.е. использовать то, что вам дает PHP). - person Scott Arciszewski; 15.06.2015
comment
@ScottArciszewski, безопасен ли ваш метод? Я создаю клон файла cookie, созданного Chrome, и помещаю его в Firefox, и пользователь вошел в систему как хром. Так это безопасно? - person AgainMe; 11.12.2016
comment
@AgainMe Защита от любой разумной модели угроз. Если кто-то может перехватить / клонировать файлы cookie (вредоносное ПО), сервер ничего не может и не должен делать с этим риском. См. Также: paragonie.com/blog/2016 / 03 / - person Scott Arciszewski; 12.12.2016
comment
Почему selector и token ?. Токена недостаточно - person Naveen DA; 07.08.2017
comment
@NaveenDA Об этом говорится в специальной статье: paragonie.com/blog/2017/02/ - person Scott Arciszewski; 13.08.2017
comment
Спасибо @Scott Arciszewski, теперь я понял - person Naveen DA; 14.08.2017
comment
последний оператор if в Re-Authenticating On Page Load не работает для меня, может ли кто-нибудь помочь? - person Termatinator; 11.03.2018
comment
Какая версия PHP у вас установлена? - person Scott Arciszewski; 12.03.2018
comment
Что означает эта строка: // Затем повторно сгенерируйте токен входа, как указано выше. Придется ли мне снова создавать селектор и аутентификатор? - person Kshitij Kumar; 09.06.2018
comment
Да, их следует использовать только один раз. - person Scott Arciszewski; 11.06.2018
comment
Вы не должны запрашивать базу данных каждый раз, когда получаете файл cookie. Вы должны убедиться, что файл cookie подписан вами. В противном случае вас может легко затопить. - person spajak; 10.07.2019
comment
Честно говоря, это не проблема. Это наводнение происходит только тогда, когда люди используют сверхдешевый виртуальный хостинг вместо собственной инфраструктуры. Но если вам необходимо это сделать, обязательно используйте PASETO вместо, скажем, JWT. - person Scott Arciszewski; 10.07.2019
comment
Я использовал эту функцию, она работает на hostgator. и я знаю, что он не такой сильный, как другие ... но он хорош для моих небольших целей $ selector = base64_encode (openssl_random_pseudo_bytes (9)); $ аутентификатор = openssl_random_pseudo_bytes (33); - person hamish; 14.07.2019
comment
@hamish Почему HostGator не позволяет вам перейти на PHP 7? Наверняка они обновят вашу версию PHP, если вы откроете заявку в службу поддержки? Если нет, установите random_compat и представьте, что вы используете PHP 7. - person Scott Arciszewski; 16.07.2019
comment
Мне нравится такой подход. Единственное, что я сделал иначе, так это то, что вместо того, чтобы полагаться исключительно на $ _SESSION ['userid'], я также установил $ _SESSION ['selector'] для проверки на auth_tokens. Таким образом, я могу аннулировать любые активные сеансы (например, в случае сброса пароля), не дожидаясь истечения срока действия сеанса PHP. - person Brian Stanback; 09.11.2019
comment
Отличное решение, спасибо! Одно дополнение, которое я сделал в разделе «Повторная аутентификация при загрузке страницы», заключалось в том, что я регенерирую токен входа в систему, а старый удаляю из базы данных. В противном случае моя база данных была быстро переполнена во время тестирования! - person Joe Coyle; 16.07.2020