На некоторой стадии развития веб-проекта возникает одна из следующих ситуаций:

  • backend перестаёт помещаться на одном сервере и требуется хранилище сессий, общее для всех backend-серверов
  • по различным причинам перестаёт устраивать скорость работы встроенных файловых сессий

Традиционно в таких случаях для хранения пользовательских сессий начинают использовать Redis, Memcached или какое-то другое внешнее хранилище. Как следствие возникает бремя эксплуатации базы данных, которая при этом не должна быть единой точкой отказа или бутылочным горлышком в системе.

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

Описание механизма


Эта идея не нова и реализована во множестве фрэймворков и библиотек для различных языков программирования. Вот пара примеров:


Стоит заметить, в Ruby on Rails делают большую ставку на производительность этого механизма в сравнении со всеми остальными методами хранения сессий и используют его по умолчанию.

Большинство имеющихся реализаций работают следующим образом: записывают в какую-то куку строку, содержащую время истечения сессии, данные сессии и HMAC-подпись времени истечения и данных. При запросе клиента кука читается соответствующим обработчиком, затем проверяется подпись и сравнивается текущее время с временем истечения сессии. Если всё совпадает, обработчик возвращает данные сессии в приложение.

Однако, шифрование куки в распространённых реализациях этого механизма отсутствует.

Сравнение с классическим подходом


В итоге, хранение сессий в куках имеет следующие достоинства:

  • Возрастает производительность веб-приложения, так как небольшая криптографическая операция дешевле сеанса сетевого обмена или доступа к диску для извлечения данных сессии.
  • Возрастает надёжность веб-приложения, так как оно не зависит от внешнего KV-хранилища. Даже если хранилище сессий обеспечено средствами отказоустойчивости, это не наделяет его абсолютной стабильностью: переключение требует времени, а часть проблем (такие как ухудшение сетевой связности между регионами) и вовсе неискоренимы. Зачастую же сессии и вовсе хранятся на единственном сервере, являющимся единой точкой отказа всего веб-приложения.
  • Экономия ресурсов. Не нужно больше хранить сессии, а значит от этого выиграют и владельцы маленьких сайтов, у которых сократится дисковая активность, и освободят несколько серверов владельцы крупных веб-проектов.

Имеются и недостатки, куда же без них:

  • Возрастает объём данных, передаваемый клиентом
  • Имеется ограничение на размер данных в сессии, связанное с ограничениями на размер кук. Обычно это чуть меньше 4 КБ кодированных данных.
  • Клиент может откатить состояние сессии на любое выданное и подписанное ранее значение, криптоподпись которого ещё действительна в текущий момент времени.

Реализации для PHP


Когда я попытался отыскать что-то похожее для PHP, я с удивлением обнаружил, что не существует ни одной библиотеки, которая дотягивает до минимума требований:

  • Безопасность: отсутствие ошибок при использовании криптографии
  • Актуальная кодовая база: поддержка современных версий PHP, отсутствие deprecated-расширений в зависимостях (таких как mcrypt)
  • Наличие тестов: сессии — это один из фундаментальных механизмов, и в основе реального приложения нельзя использовать незрелый код

Кроме этого считаю вовсе не лишним:

  • Возможность шифрования: открытое хранилище сессии на клиенте, читаемое клиентом, не всем подходит.
  • Максимально компактное представление данных — ради минимизации оверхеда и запаса ёмкости сессии
  • Встраиваемость через SessionHandlerInterface

Реализации, которые я рассмотрел:
Репозиторий Комментарий
github.com/Coercive/Cookie Фактически не библиотека для работы с сессиями вовсе. Ставит шифрованную куку, не подписывая её.
github.com/stevencorona/SessionHandlerCookie Ближе всего к требованиям, но всё же имеет значительные недостатки:
  • Потенциально уязвима к атакам по времени из-за прямого сравнения хэша с образцом
  • Нет шифрования
  • Нет тестов
  • Неэкономная упаковка куки
  • Время истечения куки не хранится со значением и не охвачено подписью. Это значит. что клиент, единожды получив данные в сессии, может воспроизводить их бесконечно.
  • Мелкие баги: read() после write() в рамках одного выполнения скрипта показывает не то, что записано и пр.

github.com/mapkyca/Encrypted-Client-Side-Sessions

Также я смотрел реализацию хранения сессий в куках в фрэймворке Slim версии 2.x, но там нет ни подписи, ни шифрования. О чём авторы сразу и предупреждают.

Почему важна проверка подписи и шифрования вместо подписи недостаточно? Во-первых, есть заметная вероятность, что кука с мусором расшифруется в какую-то сессию, особенно запись сессии короткая. Во-вторых, строка с сессией подвергается десериализации, а на вход десериализатора нельзя подавать строки из недоверенных источников.

После всех поисков я решил реализовать такую библиотеку самостоятельно.

Собственная реализация


Packagist: packagist.org/packages/snawoot/php-storageless-sessions
Github: github.com/Snawoot/php-storageless-sessions
Установка из composer: composer require snawoot/php-storageless-sessions

Ключевые особенности:

  • Обязательное шифрование. Алгоритм и режим — любой симметричный шифр на выбор, доступный в OpenSSL. По умолчанию: AES-256-CTR.
  • HMAC-подпись куки любым хэш-алгоритмом на выбор из ассортимента криптографического расширения Hash. Он же используется для генерации производных ключей шифрования. По умолчанию: SHA-256.
  • Реализованы контрмеры против атак по времени
  • Помимо основного набора данных и времени истечения, подписью охвачен и ID сессии, что оставляет простор для связывания данных сессии с внешними данными.
  • Реализация представлена в виде класса, совместимого с SessionHandlerInterface, а значит её можно прозрачно использовать практически с любыми PHP-приложениями.
  • Минимальный оверхед хранения, привносимый шифрованием и подписью.

Пара слов о выборе режима шифрования. При использовании блочных режимов шифрования (ECB, CBC) длина шифротекста незначительно возрастает. Это связано с тем, что длина исходного сообщения должна быть кратна размеру блока. Из-за обязательного паддинга прирост длины составляет от одного байта до размера блока шифра. То есть для AES — от 1 до 16 байт. При использовании потоковых режимов шифрования (OFB, CFB, CTR, ...) исходное сообщение не пропускается через блочный шифр, вместо этого блочный шифр используется для образования гамма-последовательности, и тогда длина шифротекста точно соответствует длине исходного сообщения, что лучше подходит для описываемой задачи.

Примеры использования


Небольшой скрипт, иллюстрирующий работу с этим хэндлером:

<?php

require_once("vendor/autoload.php");

header('Content-Type: text/plain');

$secret = '********************';

$handler = new VladislavYarmak\StoragelessSession\CryptoCookieSessionHandler($secret);
session_set_save_handler($handler, true);
session_start();

if ($_GET) {
    foreach ($_GET as $key => $value)
        $_SESSION[$key] = $value;
    echo "Updated session:";
} else
    echo "Current session data:\n";

var_dump($_SESSION);

Пронаблюдать его работу, задавая разные значения сессии в строке запроса, можно по адресу: https://vm-0.com/sess.php.

Пример интеграции в Symfony:

framework:
    session:
        handler_id:  session.handler.cookie

services:
    session.handler.cookie:
        class:     VladislavYarmak\StoragelessSession\CryptoCookieSessionHandler
        public:    true
        arguments:    ['reallylongsecretplease']


В качестве реального демо я подключил этот хэндлер сессий к первому пришедшему на ум веб-приложению, которое использует сессии. Им оказалось DokuWiki: wiki.vm-0.com. На сайте работает регистрация и логин, а работу сессий можно наблюдать в куках.

Благодарю за внимание и надеюсь, что эта статья поможет развитию ваших проектов.
Поделиться с друзьями
-->

Комментарии (106)


  1. Merkat0r
    04.04.2017 13:20
    -6

    Хоспаде… первое апреля же закончилось?


  1. basili4
    04.04.2017 13:20
    -8

    Мне тут кажется имеется множество уязвимостей.

    Например злоумышленник (З) может сам зарегистрироваться на сайте и будет примерно представлять содержимое шифрованной куки предположительно он может подобрать ключ к своей куки. Далее имея ключ и текст он может вычислить ключ для подписи.

    Бороться этим можно только достаточно часто менять ключи. Что приведет к коллизиям у клиента кука подписана одним ключом, а на сервере он уже сменен. значит надо постоянно слать новые куки, что прям будут напрягать интернет. + Мы не уходим от узкого горлышка нам нужно хранить ключи для каждого пользователя.

    Ну как то так.


    1. mayorovp
      04.04.2017 13:29
      +5

      На момент написания этого комментария алгоритм AES достаточно защищен от такой атаки.


  1. maxru
    04.04.2017 13:22
    +1

    Я понимаю, конечно, что много всего держать в сессии плохо, но как работать с ограничением 4КБ на 1 куку?
    Нет ли смысла в данном случае предусмотреть в том числе сохранение данных в SessionStorage?


    1. redfs
      04.04.2017 16:18
      +1

      Нет ли смысла в данном случае предусмотреть в том числе сохранение данных в SessionStorage?
      Нет, автор статьи сделал серверное решение, оно абсолютно прозрачно для клиента. Использование интерфейса Storage предполагает какую-то дополнительную логику на стороне клиента, а это из другой оперы.


  1. nazarpc
    04.04.2017 13:26
    +7

    То, что вы придумали обычно реализуют через JWT, для чего есть множество библиотек, в том числе на PHP.
    А ещё есть статьи где популярно объясняется почему реализация сессий на клиенте это плохая идея в общем и с использованием JWT в частности:
    https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid
    http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/


    Ваша реализация, судя по тому что я прочел по диагонали, идеологически похожа, так что вторая статья (и её follow-up) весьма применима и для вашего случая.


    1. mayorovp
      04.04.2017 13:33

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


      1. nazarpc
        04.04.2017 13:39
        +1

        Вы правда читали статью по второй ссылке?


        Не зависимо от того, используется JWT или другой способ шифрованного хранения сессионных данных, все претензии к подходу из статьи остаются в силе. А первая ссылка на случай если кто-то решит использовать JWT как таковой в принципе.


        1. mayorovp
          04.04.2017 13:42
          +3

          Да, читал. В обоих статьях большая часть — бред и паника на пустом месте.


          1. nazarpc
            04.04.2017 13:43
            +1

            Тогда будьте добры, уточните что во второй статье относится исключительно к JWT и не релевантно данной статье.


            1. mayorovp
              04.04.2017 13:44

              Данной статье оно как раз очень даже релевантно. Оно не релевантно для использования JWT в тех задачах, для которых JWT создавался.


            1. OlegMax
              04.04.2017 15:24

              Читаем вторую статью. «The drawbacks:

              • They take up more space
              • You cannot invalidate individual JWT tokens
              • Data goes stale
              • Implementations are less battle-tested or non-existent»

              Каждый пункт как-то отражен в данной статье, автор про это задумывался. Так что корректно было бы обсуждать каждый момент по существу, а не просто кидать ссылку.


              1. nazarpc
                04.04.2017 15:41

                Ссылку я кинул для того, чтобы не повторять то, что кто-то уже структурировал в виде статьи. Задумываться над этим мало, нужно иметь решение к вопросам. Но исходя из их сущности решения скорее всего быть не может в общем случае. Если считаете иначе — давайте обсуждать.


                Опять таки, чтобы не повторяться, рекомендую прочесть саркастический ответ на часто встречаемые контр-аргументы (он упомянут в шапке той статьи, если пропустили): http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/


                1. OlegMax
                  04.04.2017 15:54
                  +4

                  В статьях, на которые вы ссылались, какие-то люди пытаются попиариться на относительно модном JWT. А реальность такова, что есть pros и cons у JWT-like и server-side сессий. У обоих есть фунадаментальные достоинства и недостатки и есть дефекты реализаций/спецификаций.
                  Надо стремиться использовать наиболее подходящее решение в каждом случае и исправлять дефекты, но заявлять «JWT sucks», как авторы тех статей — неправильно.


                  1. nazarpc
                    04.04.2017 17:40

                    Голословно — не правильно. А если аргументировано, указывая на фундаментальные ошибки и заблуждения, то вполне себе можно и нужно.


        1. zerkms
          04.04.2017 14:12
          +1

          JWT или другой способ шифрованного хранения сессионных данных


          В JWT, кстати, данные хранятся в открытом виде.


          1. nazarpc
            04.04.2017 14:15
            +1

            Не обязательно


            1. zerkms
              04.04.2017 14:19

              Обязательно, согласно спецификации https://tools.ietf.org/html/rfc7519


              1. nazarpc
                04.04.2017 14:23
                +1

                Вы Abstract читали (первый параграф)?:


                JSON Web Token (JWT) is a compact, URL-safe means of representing
                claims to be transferred between two parties. The claims in a JWT
                are encoded as a JSON object that is used as the payload of a JSON
                Web Signature (JWS) structure or as the plaintext of a JSON Web
                Encryption (JWE) structure, enabling the claims to be digitally
                signed or integrity protected with a Message Authentication Code
                (MAC) and/or encrypted.

                Весьма однозначно сказано что содержимое вполне себе может быть зашифрованным.


                1. zerkms
                  04.04.2017 14:25

                  Так, как на хабре минусовать самого себя? :-D

                  Я всегда думал, что JWE это самостоятельный тип токена, отличный от JWT, пардон.


              1. mayorovp
                04.04.2017 14:28

                The contents of the JOSE Header describe the cryptographic operations applied to the JWT Claims Set. If the JOSE Header is for a JWS, the JWT is represented as a JWS and the claims are digitally signed or MACed, with the JWT Claims Set being the JWS Payload. If the JOSE Header is for a JWE, the JWT is represented as a JWE and the claims are encrypted, with the JWT Claims Set being the plaintext encrypted by the JWE. A JWT may be enclosed in another JWE or JWS structure to create a Nested JWT, enabling nested signing and encryption to be performed.
                https://tools.ietf.org/html/rfc7519#page-6


    1. OlegMax
      04.04.2017 15:28
      +2

      Первая статья — типа «Давайте не будем использовать HTTP — вон в нём сколько дыр уже нашли». Какой может быть ответ? Нет, спасибо, мы дырки-то позатыкаем, но использовать не бросим.


  1. lair
    04.04.2017 13:27
    +6

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

    … только до тех пор, пока "данные сессии" — маленькие. В противном случае вы получаете гигантский оверхед на каждом запросе.


  1. VolCh
    04.04.2017 14:00

    Как решается вопрос принудительной инвалидации сессии?


    А вообще, по-моему, данный подход не является сессией в обычном смысле слова в веб-разработке. Сессия — хранение каких-то данных на стороне сервера такое, что результаты запросов клиента зависят от истории предыдущих запросов. Тут же каждый запрос получается самодостаточным, что-то вроде WebForms.


    1. mayorovp
      04.04.2017 14:16

      А WebForms-то тут при чем?..


      1. Sioln
        04.04.2017 14:25
        +1

        Думаю, что это отсылка к ViewState, где используется механизм 1:1 — генерируется состояние, шифруется, подписывается, кладётся в форму.


        1. mayorovp
          04.04.2017 14:29

          Да, но ViewState работало в дополнение к сессиям, а не вместо них...


      1. VolCh
        04.04.2017 15:13

        Может запамятовал, но вроде они отправляли с фронта на сервер всё состояние сессии, ну и получали его от сервера.


        1. lair
          04.04.2017 15:19

          Они отправляли на сервер все состояние страницы. Сессия работала отдельно.


        1. mayorovp
          04.04.2017 15:20
          -1

          Они отправляли состояние страницы, а не сессии. Если у пользователя открыты сразу две вкладки — эти понятия принципиально различаются.


          Кроме того, ViewState не сохраняется при переходах между страницами.


          1. VolCh
            04.04.2017 15:22

            Может. Я особо не разбирался, как фротендер с ними только немного работал больше 10 лет назад.


    1. MikalaiR
      04.04.2017 14:17
      +1

      А он никак не решается. Разве что костылем в виде хранилища отозванных сессий.


      1. VolCh
        04.04.2017 15:13
        -2

        В обычных сессиях решается удалением файла/записи сессии.


        1. VolCh
          04.04.2017 16:16

          Не решается?


    1. YourChief
      05.04.2017 13:06

      Добрый день!

      Как и указано в недостатках,

      Клиент может откатить состояние сессии на любое выданное и подписанное ранее значение, криптоподпись которого ещё действительна в текущий момент времени.
      Однако здесь важно заметить: если, к примеру, стоит практическая задача разлогинить юзера, то инвалидация одной сессии сама по себе ничего не даёт. Нужно прервать все сессии. И здесь у классического подхода к хранению сессий тоже возникают проблемы, так как в таком случае нужно найти и удалить все сессии, которые принадлежат пользователю. То есть это значит нужно хранить и поддерживать соответствие пользователя списку сессий. Дальше — больше: если используются sticky-сессии, то тогда один сервер должен пошариться по множеству других, чтобы разлогинить юзера через удаление сессий.

      Удаление сессий в любом случае непрактичный подход. Вместо этого в сессию записывают факт логина вместе со временем логина. А в профиле пользователя держат время последнего логаута. Если время логина сессии раньше времени последнего логаута — считать её неактивной. При таком подходе для логаута нужно всего лишь обновить одно значение в записи о пользователе.


      1. lair
        05.04.2017 13:35
        -2

        А в профиле пользователя держат время последнего логаута.

        И как, бишь, вы это время между разными серверами будете синхронизировать в случае sticky sessions?


        (и как вы будете решать задачу "разлогинь меня в этой сессии, не трогая остальные"?)


        1. YourChief
          05.04.2017 14:11

          И как, бишь, вы это время между разными серверами будете синхронизировать в случае sticky sessions?
          Время логаута не нужно синхронизировать, оно в профиле пользователя
          (и как вы будете решать задачу «разлогинь меня в этой сессии, не трогая остальные»?)
          Если нужно предоставить пользователю возможность разлогинить любую сессию, на которую он покажет пальчиком, то и при том и том подходе нужно хранить список сессий. Если только ту, в которой пользователь прямо сейчас сидит, то у классического подхода тут преимущество. Обязательно пользуйтесь классическим подходом, если Ваша бизнес-задача — разлогинивать текущие сессии пользователей.


          1. lair
            05.04.2017 14:13
            -1

            Время логаута не нужно синхронизировать, оно в профиле пользователя

            … который хранится где?


            Если нужно предоставить пользователю возможность разлогинить любую сессию, на которую он покажет пальчиком, то и при том и том подходе нужно хранить список сессий.

            После чего совершенно не понятно, зачем гонять данные сессии на клиента.


            1. YourChief
              05.04.2017 14:31
              -1

              … который хранится где?
              Который хранится в базе, а не в сессии.
              После чего совершенно не понятно, зачем гонять данные сессии на клиента.
              В таком случае заведите ишью в репозиториях джанги и рельсов, потребуйте немедленно удалить непонятные бэкенды для сессий.


              1. lair
                05.04.2017 15:27

                Который хранится в базе, а не в сессии.

                … которая становится той же самой единой точкой отказа. В чем разница-то?


                1. YourChief
                  05.04.2017 16:27

                  База это база, а хранилище сессий — это хранилище сессий. Предложенный хандлер устраняет SPOF в виде хранилища сессий.


                  1. lair
                    05.04.2017 16:30

                    Так что мешает хранить сессии в той же БД, что и профили? Как была одна точка отказа, так и осталась.


                    1. YourChief
                      05.04.2017 16:32

                      Вы когда-либо хранили сессии в БД?


                      1. lair
                        05.04.2017 16:32

                        Да.


                        1. YourChief
                          05.04.2017 17:10

                          Отлично. Интересно узнать ответы на два вопроса:

                          1. Как вы удаляли старые сессии?
                          2. Каков ваш план масштабирования пропускной способности базы на запись при росте числа сессий?
                          Напоминаю при этом, что подход с хранением сессий в куках сам по себе не полагается на базу, если приложение не использует базу вовсе, то и для хранения сессий она не нужна. Если вдруг стоит требование инвалидировать сессии, то решение возможно чтениями из базы, что, согласитесь, совсем другое дело.


                          1. lair
                            05.04.2017 17:16

                            Как вы удаляли старые сессии?

                            Регулярной задачей.


                            Каков ваш план масштабирования пропускной способности базы на запись при росте числа сессий?

                            Шардинг.


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

                            … напоминаю, что подход с хранением сессий в куках работает только при малом объеме данных в сессии.


  1. L0NGMAN
    04.04.2017 14:00

    Чем вам Memcached не угодила?


    1. lexore
      04.04.2017 15:28

      Автор статьи спросил бы у вас: Допустим, memcache упал/втупил/стал недоступен. У вас или лежит сайт, или все незалогинены.


      От себя замечу, что если вам хотя бы чуть-чуть важны залогиненые пользователи, под сессии лучше использовать redis.
      Там есть и сохранение на диск, и реплики, и кластер.


      1. L0NGMAN
        05.04.2017 08:28
        -1

        Ведь в Memcached тоже есть реплики, кластер? А Redis ещё и имеет ряд недостатков для сессии, на пример session locking https://github.com/phpredis/phpredis/issues/37
        Я не использовал Redis в highload пректе и не могу лично от себя ничего сказать…


        1. lexore
          05.04.2017 15:52

          Да, в memcache прикрутили что-то для репликации и можно использовать несколько серверов memcached в качестве кластера.
          Но данные при перезагрузке ноды вы все равно потеряете.
          В highload redis отлично используется для хранения сессий, к нему обращаются php, python, go.


          По вашей ссылке это проблема не redis, а конкретного php extension.
          Там в обсуждении люди описали, как сделать свой handler с session locking.
          Я не программист, но лично мне кажется, что session locking нужно делать в приложении, а не внутри extension, так как это специфичная фича — каждый может видеть её реализацию по разному.


  1. vlreshet
    04.04.2017 14:14
    -5

    Удивлён что ещё никто не запостил картинку с троллейбусом из буханки хлеба. Механизм cookie предназначен не для этого, с таким же приколом можно хранить сессию в localStorage. Только зачем? И кстати, вот пятой точкой чувствую что возможны баги при параллельных запросах. В такие моменты у пользователя будет возникать несколько копий сессии, каждая из которых может обработаться по-своему.


    1. vlreshet
      04.04.2017 16:06
      -4

      Минусов ляпнули — и сбежали. Хоть бы обосновали что-ли…


      1. Aleks_ja
        05.04.2017 12:28
        +1

        На самом деле на ваш вопрос «зачем» есть ответ в самом начале статьи:

        На некоторой стадии развития веб-проекта возникает одна из следующих ситуаций:

        backend перестаёт помещаться на одном сервере и требуется хранилище сессий, общее для всех backend-серверов
        по различным причинам перестаёт устраивать скорость работы встроенных файловых сессий

        Традиционно в таких случаях для хранения пользовательских сессий начинают использовать Redis, Memcached или какое-то другое внешнее хранилище. Как следствие возникает бремя эксплуатации базы данных, которая при этом не должна быть единой точкой отказа или бутылочным горлышком в системе.


        Я вот никогда раньше и не задумывался, что можно так сессии хранить. Теперь обдумываю…


  1. lexore
    04.04.2017 15:33
    +1

    Замечу, что большие куки — это плохо. Несколько раз у разных подсистем (не php) ловили падения при большом объеме кук. Ловить такие ошибки на проде — всегда очень занимательная задача.


  1. dreammaster19
    04.04.2017 15:39
    +1

    Так и не понял ради чего всё это сделано? Если хотите работать с куками, то пожалуйста, зачем тогда вообще дергать сессию (а уж шифровать или нет это уже зависит от нужд). Кроме того не обязательно дергать диск или сетевые протоколы для сессий, можно хранение и в памяти сделать (если так критично к скорости чтения сессий). Кроме того, если у вас SSD, то у скорость чтения с диска довольно большая.


    1. VolCh
      04.04.2017 16:18
      +1

      Для того, чтобы перенести хранение сессий на сторону клиента без изменения механизма работы с ними.


      В памяти хранить сложно, если у вас несколько инстансов апп-сервера — нужен механизм репликации (причём мастер-мастер) памяти между серверами.


      1. dreammaster19
        04.04.2017 19:05

        in memory nosql base
        И все таки проще и логичнее просто перейти на работу с куками без сессий. С учетом того, что во многих фрейморках почти всега используются классы обертки для работы с сессиями. Просто в них использовать данные из куков. Но как пример работы через сессий через куки — довольно хороший)


        1. VolCh
          04.04.2017 19:10
          -1

          Начало поста:


          Традиционно в таких случаях для хранения пользовательских сессий начинают использовать Redis, Memcached или какое-то другое внешнее хранилище. Как следствие возникает бремя эксплуатации базы данных, которая при этом не должна быть единой точкой отказа или бутылочным горлышком в системе.

          Однако, есть альтернатива этому подходу...

          Не факт, что проще и логичней, не очень хорошо и в классы-обёртки сессий фреймворков лезть, и переписывать код, если $_SESSION используется.


  1. Interreto
    04.04.2017 15:39
    -1

    Синхронизация между устройствам? Вот вы зашли на сайт с рабочего компа, затем в транспорте с планшета (еще и с 3G), что будет?


    1. mayorovp
      04.04.2017 15:43
      +1

      Ну, это болезнь любых сессий вообще, а не только данной реализации.


      1. vlreshet
        04.04.2017 16:06

        Но если мы храним сессию и так у себя — то нам легко раздать её на все клиенты и таким образом засинхронить. А если использовать вот это извращение с куками — тогда надо у одного устройства сессию взять, у себя её сохранить, и на другое устройство отдать. Если такой синхрон будет происходить постоянно — то вся идея ломается на корню, ибо мы опять храним сессии у себя.


        1. mayorovp
          04.04.2017 16:09
          +7

          Если раздать ее на все клиенты и таким образом засинхронить — это будет уже не сессия, а профиль пользователя.


  1. imbasoft
    04.04.2017 16:37
    -3

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

    1. HMAC — это не электронная подпись. Поскольку строится на базе симметричного ключа и соответственно может быть подделана одной стороной без возможности это опровергнуть другой стороной. Классическая ЭП на ассиметричной криптографии этого не допускает, поскольку ЭП строится с помощью закрытого ключа который есть только у подписывающей стороны.

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

    Пример. Вася подключается в Web-клиенту Банка. К нему грузятся скрипты, содержащие ключ шифрования.
    Вася отключается, но ключ у него есть. Вася знает что у Зины на счету много денег. Он от имени Зины строит сессию (ему даже авторизовываться не надо) и совершает перевод.

    3. Вы скорее всего скажите, что сессия будет защищена TLS. Но раз так, тогда смысла в доп. шифровании нет.

    Для исправления ситуации необходимо как минимум для каждой сессии получать рандомный ключ шифрования. Но даже это не защитит от атак повтором. Поэтому нужно капать в сторону использования ассиметричной крипты.


    1. mayorovp
      04.04.2017 16:45
      +1

      Вы пост-то вообще читали? Сторона тут только одна — сервер, который сначала генерирует куку, а потом ее же читает.


      1. imbasoft
        04.04.2017 17:14
        -3

        Что вы называете сессией?

        Если только некий id, тогда непонятно причем тут ограничения на 4кб в куку, если же в сессии присутствует состояние полное состояние клиента, то для того чтобы его нехранить на сервере его надо шифровать и оно должно шифроваться на клиенте.

        В статье схемы взаимодействия не хватает.


        1. mayorovp
          04.04.2017 18:07
          +1

          Веб-сессия пользователя, оно же сеанс — это понятие, объеденяющее активность пользователя по взаимодействию с веб-ресурсом с момента первого обращения к ресурсу и до прекращения взаимодействия ресурсом.


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


          Клиентскому же коду доступ к сессии не нужен — у него есть localStorage для тех же целей.


  1. da411d
    04.04.2017 16:43
    -2

    Я, например, генерировал id сессии хешем от юзерагента, ip, даты и секретного ключа.
    И не сохранял в куках, а просто генерировал при каждом запросе.


    1. mayorovp
      04.04.2017 16:46

      Даты чего?


      1. da411d
        04.04.2017 16:47

        Даты создания сессии


        1. mayorovp
          04.04.2017 16:51
          -1

          Я не понимаю. Где вы храните дату создания сессии, если id сессии генерируется на ее основе?


          1. da411d
            04.04.2017 17:26
            -3

            Дату берем сегодняшнюю.


            1. basili4
              04.04.2017 17:35
              +2

              а что будет в 23:59? Клиент разлогонится. Очень мило, можно еще сервер выключать. со словами пора спать


            1. mayorovp
              05.04.2017 09:42
              +1

              Отлично. Теперь любые два пользователя, которые сидят с одного IP и одного браузера в один день имеют общую сессию!


              Вы что за сайты-то делаете, если ваша целевая аудитория, по всей видимости, бездетные незамужние люди, не сидящие за NATом?..


              1. mayorovp
                05.04.2017 09:46

                PS Пост в тему — https://habrahabr.ru/post/108493/


    1. Interreto
      04.04.2017 18:30

      Браузер обновился — поменялся хэш. Надо брать не весь юзер агент, а из него платформу + ip + time zone,


      1. da411d
        04.04.2017 18:55

        Согласен. Просто в моем случае лучше чтоб "в любой непонятной ситуации разлогинивался". Но сам метод можно модифицировать под конкретную ситуацию.


  1. olku
    04.04.2017 18:23
    -3

    1. YourChief
      05.04.2017 13:17

      Добрый день!

      Я прицельно искал такие реализации в поиске хабра, но не смог найти Вашу статью. Но сейчас я вижу, что даже найдя её, я бы её ни в коем случае не выбрал.

      Во-первых, IV это не публичный ключ, и хранить его отдельно от сообщения нет смысла.
      Во-вторых, нет подписи куки, и я писал в статье, почему она должна быть.


      1. olku
        05.04.2017 13:26

        Дело хозяйское.

        1. Имеет, инвалидация же.
        2. Шифрование же.


        1. YourChief
          05.04.2017 13:33

          Вы свято верите, что кука в браузере протухнет по истечении времени и тогда данные инвалидируются. Однако, клиент сам управляет куками, они ему подконтрольны полностью. Вот в том числе поэтому нужно время экспайра и его подпись. Но не только поэтому, как я уже говорил.


          1. olku
            05.04.2017 13:38

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


            1. YourChief
              05.04.2017 13:52

              Я почитал ваш код и утверждаю: куки подконтрольны клиенту, какие он проставит, такие и будут. Если клиент решит их хранить вечно — в вашем случае сессия будет длиться вечно.


              1. olku
                05.04.2017 14:15

                Идентификатор — да, данные за ним — нет. Они не передаются клиенту, как и у файловой сессии, у которой есть собственный GC. Я, например, использую xcache с ненулевым временем жизни и вектор для ключа. Промах приводит к регенерации, никаких «вечно».


          1. oxidmod
            05.04.2017 13:45

            нет, если httpOnly


            1. olku
              05.04.2017 14:17

              можно, МИТМом, локальным прокси например, если очень надо


              1. oxidmod
                05.04.2017 18:52

                как ві секьюрную куку митмом прочитаете?


                1. olku
                  05.04.2017 20:06

                  Вышеуказанным методом. Например, org.littleshoot.proxy… Или что Вы хотели спросить?


                  1. oxidmod
                    13.04.2017 12:32
                    -1

                    session.cookie_secure=True
                    session.cookie_httponly=True
                    


                    ?


                    1. lair
                      13.04.2017 13:22

                      И что? Клиент-то ее получил и прочитал, значит, и обратно послать может. Все эти флажки "безопасности" — они для добронамеренных клиентов.


                      1. oxidmod
                        13.04.2017 15:52

                        1. Куки с флагом HttpOnly не видны браузерному коду
                        2. Куки с флагом Secure пересылается только через HTTPS (HTTP с использованием SSL — Secure Socket Level)

                        Итак, каким образом вы организуете mitm — атаку, чтобы браузер юзера не выдал вашего вмешательства?


                        1. lair
                          13.04.2017 15:56

                          Куки с флагом HttpOnly не видны браузерному коду

                          Это, простите, как? А отправляет их кто?


                          Куки с флагом Secure пересылается только через HTTPS (HTTP с использованием SSL — Secure Socket Level)

                          Только если клиент выполняет это требование. И да, даже если клиент выполняет это требование, он может не выполняеть требование про валидацию сертификатов, или сертификаты могут быть подменены на уровне устройства и так далее, далее, далее.


                          1. oxidmod
                            13.04.2017 16:01

                            Это, простите, как? А отправляет их кто?

                            Это значит что их не видит js.
                            Сам браузер их конечно шлет с каждым запросом, но js не может их не то что поменять, но даже считать.

                            Только если клиент выполняет это требование. И да, даже если клиент выполняет это требование, он может не выполняеть требование про валидацию сертификатов, или сертификаты могут быть подменены на уровне устройства и так далее, далее, далее.


                            Да, всегда есть идиоты вводящие данные карты не пойми на каких сайтах. Но вменяемый человек обратит внимание, что чтото страничка его банкинга говорит о несекьюрном конекте и предлогает продолжить на свой страх и риск.


                            1. lair
                              13.04.2017 16:26

                              Так вот, клиенты браузерами не ограничиваются. Поэтому если у нас есть способ куку перехватить (а если его нет, то все обсуждения, в общем-то, на пустом месте), то мы всегда можем ее отправить и сделать подмену сессии.


                              1. oxidmod
                                13.04.2017 16:33

                                1. Куки для API?
                                2. Даже если куки для API, то они защищены ssl, а значит расшифровать перехваченный трафик вы всеравно не можете


                                1. lair
                                  13.04.2017 16:53

                                  Куки для API?

                                  Нет, для подконтрольного агента.


                                  Даже если куки для API, то они защищены ssl, а значит расшифровать перехваченный трафик вы всеравно не можете

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


                                  1. VolCh
                                    14.04.2017 14:44

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


                                    1. lair
                                      14.04.2017 14:46

                                      Для этого надо пользователя инвалидировать целиком.


                                      1. VolCh
                                        14.04.2017 14:49

                                        Пользователя инвалидировать не проблема, проблема инвалидировать все его открытые сессии, если принято решение на каждый запрос не проверять не запрещён ли с момента последнего запроса вход пользователю.


                                        1. lair
                                          14.04.2017 15:17

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


                                          Опять-таки, есть разница между инвалидацией куки и инвалидацией сессии.


  1. He11ion
    04.04.2017 18:31

    Основная и ключевая проблема на мой взгляд — инвалидация данных сессии.
    Да, единая точка отказа и бутылочное горлышко в виде редиса/мемкшеа есть, но уже существует множество решений ждя больших объемов данных


  1. Lewolf
    05.04.2017 18:06

    Автор пишет, что у него были проблемы с поиском текущих реализаций данного механизма на PHP.

    Справедливости ради, стоит отметить, что в Laravel один из драйверов сессий, которые идут с фреймворком по умолчанию, предназначен именно для хранения данных сессии в куках.

    https://laravel.com/docs/master/session


    1. YourChief
      05.04.2017 21:53

      Посмотрел реализацию в ларавеле:

      1. Громоздкая сериализация: ключи в жсон прям текстом пишутся, от всех полей в base64 паддинг ("===") остаётся, могли бы трансляцию в urlsafe base64 сделать, подпись прям в строковом виде. Можно было бы снизить расходы этой части раза в полтора точно.
      2. Экспайр не охвачен подписью, то есть сессии — всегда вечные.

      В остальном всё очень хорошо сделано.


  1. thecoder
    13.04.2017 10:48
    -1

    С академической точки зрения интересно. Но если подумать, что такое сессия? Это некий ключ к состоянию. Хранить состояние на клиенте? Ну да, можно, если это простая корзина. Корзину даже шифровать не надо. user_id хранить даже в зашифрованном виде я бы не рискнул.

    Состояние может быть очень сложным. Дополнительные сложности в отладке ради экономии 1 запроса, но платить за это жирнющей кукой — сомнительная выгода.


    1. VolCh
      13.04.2017 11:48

      Сессия — не ключ к состоянию, а само состояние. Хранение состояния сессии на клиенте — ключ к созданию стейтлесс серверов, например в целях балансировки нагрузки и отказоустойчивости.


      1. lair
        13.04.2017 11:53

        Хранение состояния сессии на клиенте — ключ к созданию стейтлесс серверов, например в целях балансировки нагрузки и отказоустойчивости.

        … один из возможных.


        1. VolCh
          13.04.2017 11:54

          Я не писал "единственный"