Нордавинд
Компания
41,88
рейтинг
1 октября 2013 в 18:44

Разработка → Храним сессии на клиенте, чтобы упростить масштабирование приложения (3-я из 12 статей о Node.js от команды Mozilla Identity) перевод tutorial

От переводчика: Это третья статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona. Эта статья посвящена применяемому в Persona способу хранения данных сессии на клиенте.




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

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

Масштабирование сайта с хранением состояния


Если необходимо масштабировать такой сайт, есть три варианта:

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

У всех этих подходов есть недостатки:

  1. Репликация ухудшает производительность и увеличивает сложность.
  2. Центральное хранилище ограничивает возможность масштабирования и приводит к дополнительным задержкам.
  3. Привязка пользователей к конкретным серверам приводит к проблемам, когда сервер отключается.

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

Хранение сессий на клиенте


У хранения данных сессии в браузере есть несколько очевидных преимуществ:

  1. Данные доступны всегда, независимо от того, какой сервер обслуживает клиента.
  2. Не надо хранить состояние на сервере.
  3. Не надо синхронизировать информацию о состоянии между серверами.
  4. Можно очень легко добавлять новые серверы.

Но есть одна большая проблема: данным, хранящимся у клиента, нельзя доверять. К примеру, если вы храните в cookie ID пользователя, то он может подменить его и получить доступ к чужой учётной записи.

Хоть эта проблема и кажется непреодолимой, у неё есть решение: хранить данные в защищённом контейнере. Таким образом, больше нет необходимости доверять клиенту — у него нет возможности незаметно их подменить.

На практике это означает, что данные в cookie надо зашифровать и подписать ключом, хранящимся на сервере. Именно этим занимается модуль client-sessions.

node-client-sessions


Библиотека node-client-sessions для Node.js заменяет стандартные middleware-модули фреймворка Connect session и cookie-parser. Вот как включить её в простое приложение для Express:

const clientSessions = require("client-sessions");
 
app.use(clientSessions({
  secret: '0GBlJZ9EKBt2Zbi2flRPvztczCewBxXK' // set this to a long random string!
}));

Затем можно устанавливать значения свойств объекта req.session:
app.get('/login', function (req, res){
  req.session.username = 'JohnDoe';
});

и считывать их:
app.get('/', function (req, res){
  res.send('Welcome ' + req.session.username);
});

Для закрытия сессии служит метод reset():
app.get('/logout', function (req, res) {
  req.session.reset();
});

Мгновенное закрытие сессий Persona


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

При хранении данных сессии на сервере достаточно просто удалить данные сессии из БД, после чего все cookie на всех клиентах буду указывать на несуществующую запись. Когда данные хранятся на клиенте, сервер не может быть уверен, что данные были удалены во всех браузерах. Другими словам, не так то просто синхронизировать новое состояние сервера (пользователь завершил сессию) с состоянием на клиенте (сессия открыта).

Чтобы немного снизить остроту проблемы, client-sessions хранит в cookie время жизни сессии. Прежде чем распаковывать данные из зашифрованного контейнера, сервер проверит, не просрочены ли они. Если да, то он проигнорирует эти данные и будет считать, что сессия закрыта.

Хотя схема с установкой времени жизни работает неплохо, особенно, если время не очень большое, в случае с Persona нам нужен был способ немедленно закрывать сессию на всех клиентах, если пользователь менял пароль.

Нам всё же пришлось хранить малую толику состояния на сервере. Мы сделали это, добавив одно поле в таблицу БД на сервере и в cookie.

Каждый вызов API, который обращается к данным сессии, теперь считывает это поле в БД и сравнивает его с аналогичным полем в cookie. Если они не совпадают, сессия считается закрытой.

Лишнее обращение к БД — это, конечно не очень хорошо, но, к счастью, нам и так приходилось читать из таблицы с данными пользователей при почти каждом вызове, так что идентификатор сессии можно было получить без лишних накладных расходов. Чтобы вы могли быстрее начать экспериментировать с модулем, мы написали небольшое демонстрационное приложение.




Автор: @ilya42 François Marier
Нордавинд
рейтинг 41,88
Компания прекратила активность на сайте

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

  • +9
    А почему не хранить в этой бд теперь и саму сессию?)
    • 0
      И, например, использовать Redis для хранения сессий, который подходит отлично: быстрый доступ по ключу (идентификатору сессии), удобный инструментарий для сериализации/десиарилизации, надежность не критична.
      И все таки я бы не доверял шифрованным кукам. Какой алгоритм шифрования у них используется?
    • 0
      Ага, непонятно, если они все равно сверяются с БД. Вообще какая-то странная штука, статические сайты конечно хорошо масштабируются, но они же сами говорят про БД, а это уже не статика. Шифрование cookies ключем сервера вообще относительно распространненая вещь, например у IIS/ASP.NET (MachineKey)
    • +1
      Заголовок статьи как бы намекает — «чтобы упростить масштабирование приложения».
      Есть у вас, к примеру, 20 web серверов с балансировкой. Пускай на каждом есть своя БД и между ними настроена репликация.
      Ответ на вопрос «Как будет чувствовать себя репликация, если данные будут меняться в n-раз чаще?» совпадает с ответом на Ваш вопрос.

      Можно пойти дальше и добавить сайты на других доменах, партнерский сайты, разные дата центры, мобильные приложения и т.д.
  • +1
    Обычно при шифровании да ещё и подписи результат довольно большой по размеру. Cookies гоняются туда-сюда на «каждый чих». Насколько это влияет на нагрузку сети, user expirience?

    В принципе я встречался с фреймворками на Java и C#, которые хранят сессию в cookies или передают параметром при каждом запросе, если cookies недоступны. Но мне показалось, что это плохое решение.
    • +2
      Взгляните на комментарии к оригиналу. Там тоже дискуссия примерно на эту тему. Резюмируя в двух словах: такой способ хранения сессий далеко не всем подойдёт, конкретно для Persona он подошёл хорошо, разработчики довольны. Возможно дело в том, что Persona используется прежде всего для аутентификации на других сайтах, то есть сеанс работы с ней очень короткий, и то, что на каждый запрос гоняется толстая кука — не проблема. С другой стороны, им дорога каждая миллисекунда задержки, чтобы не раздражать пользователя долгой аутентификацией. Наверное, из-за такой специфики сценариев использования им этот метод и подошёл.
    • 0
      Результат не сильно большой, если данных хранится не много + нужно использовать gzip.
      Если БД не на том же сервере, то сеть в любом случае будет грузится этими данными.

      Из реального опыта — кука по размеру сравнима с картинкой логотипа twitter(700 Байт), тока на куку не надо делать отдельный запрос.

      Есть еще минус(помимо ранее упомянутых) — если статика раздается с того же домена принимающего куки и тогда данные сессии слишком много раз будут передаватсья и разбираться web сервером.
  • +1
    Безопасность, по сравнению с сессиями, снизилась. Проблемы остались те же. Где профит?
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Привет, __VIEWSTATE! За который столько матерят asp.net…
  • 0
    1. в такой сессии много данных не сохранишь, ибо есть ограничение на размер куки (желательно не превышать 4кб)
    2. понимаю что перевод, но все же
    Хотя схема с установкой времени жизни работает неплохо, особенно, если время не очень большое, в случае с Persona нам нужен был способ немедленно закрывать сессию на всех клиентах, если пользователь менял пароль.

    не проще ли было дополнительно шифровать сессию еще и с помощью пароля пользователя? тогда не надо было бы и в бд ничего хранить
    • 0
      1. gzip + разделение на несколько cookie
  • 0
    Т.е. сначала мы хранили у клиента ключ, у себя сессию. Теперь мы будем хранить у себя ключ, а у клиента сессию.
    Так как при каждом запросе передаются cookie, то всю сессию мы будем гонять туда-сюда. В чем, собственно, плюс?
  • 0
    Почему время жизни сессии не зашифровано? Его же в таком случае можно изменить, продлив время жизни сессии.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка