JWT и одна (!) сессия на пользователя / без параллельных сессий

Наше текущее приложение использует сеансы HTTP, и мы хотели бы заменить его на JWT.

Установка разрешает только один сеанс для каждого пользователя. Это означает:

  1. User signs in at Device 1
    • User is logged in at Device 1 (new Session created)
  2. User signs in at Device 2
    • User is logged in at Device 2 (new Session created)
    • Пользователь не вошел в систему на устройстве 1 (сеанс был уничтожен)

Это работает, потому что между идентификатором сеанса и идентификатором пользователя существует связь на стороне сервера.


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

  1. User signs in at Device 1
    • JWT tokens signature contains counter+1 (and save new counter to database)
  2. User signs in at Device 2
    • JWT's signature contains counter+1 and it gets increased and saved to db.

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

Это каким-то образом делает его с сохранением состояния. :(

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


Есть ли другое решение для предотвращения одновременных входов в систему? Может быть, что-то, что работает без доступа к базе данных и сохраняет ее без состояния?


person Benjamin M    schedule 23.02.2015    source источник
comment
Возможно, вы сможете создать пользовательское утверждение в JWT, когда подписываете и отправляете клиенту JWT. Когда они отправят вам токен, убедитесь, что конкретное утверждение, такое как, например, «device_id», не изменилось. Это немного сложно, но вам, возможно, придется немного поработать с сеансами на стороне сервера, даже если вы пытаетесь этого избежать.   -  person Signus    schedule 04.06.2015


Ответы (5)


Вы очень близки к разгадке.

Для этого вам потребуется следующее:
1. Включите в токен iat (время выдачи токена)
2. Где-нибудь сохраните время, когда пользователь в последний раз входил в систему, например, в профиле пользователя.

Теперь при проверке токена сделайте дополнительную проверку: iat (выпущено в) должно быть не позднее времени последнего входа в систему. Это неявно делает недействительными старые токены.

person The Tahaan    schedule 21.07.2017
comment
Спасибо. Этот ответ на комментарий был полезен. - person an0nh4x0r; 05.07.2019
comment
Отличная стратегия! - person moeabdol; 10.08.2019
comment
Это решение обеспечивает проверку токена с отслеживанием состояния. - person Réda Housni Alaoui; 14.08.2019
comment
С сохранением состояния означает хранение информации о сеансе на сервере. Хранение состояния в БД, что я и предложил, НЕ делает его удовлетворительным решением. - person The Tahaan; 14.08.2019
comment
Однако, если вы используете какую-либо микросервисную архитектуру или кластеризацию, автомасштабирование и т. д., отслеживание этого на сервере потребует дополнительного уровня сложности, например, в случае, если новый запрос на вход будет сбалансирован по нагрузке на другой экземпляр сервера. - person Pasha Skender; 05.05.2020

Как насчет закрытия сеанса пользователя на любом другом устройстве.

Как насчет. каждый раз, когда пользователь входит в систему, вы сохраняете последний вход в систему по типу устройства и отправляете push-уведомление всем подключенным устройствам одного типа (предположительно одному)?

В этом случае в браузере вы можете отправить push-уведомление в браузер, просто проверьте, что произойдет, если этот браузер в данный момент закрыт?

В случае мобильных приложений вы можете отправить push-уведомление мобильному приложению с инструкцией закрыть

person Jose Pato    schedule 12.06.2019

Это другое решение, которое может быть более подходящим в некоторых ситуациях.

Вам все равно нужно зайти в БД, но предполагая, что у большинства ваших пользователей есть только одно устройство и только у некоторых есть второе устройство, вы можете использовать следующую стратегию:

Во время входа в систему (запрос нового токена) позвольте клиенту предоставить идентификатор устройства. Сравните это со значением «last_device» пользователя. Если он отличается, это означает, что пользователь перешел на новое устройство.

В этом случае добавьте запись epoc для этого пользователя в специальную таблицу.
userid: уникальная ссылка на пользователя (идентификатор), не равная null
epoc: метка времени

Идея состоит в том, что эта таблица может быть намного меньше, чем полная пользовательская таблица. Только пользователи, которые недавно вошли в систему более чем с одного устройства, будут иметь запись в этой таблице. Так что сканирование этой таблицы эффективно. После проверки токена обычным способом убедитесь, что iat (выпущено в) не предшествует эпохе сеанса пользователя. Если это так, то это устройство не является последним зарегистрированным устройством.

У этого решения есть и другие применения: оно позволяет пользователю удаленно выйти из системы (вы создаете новую запись для пользователя с текущим временем, что фактически делает недействительными все существующие для него токены).

Поддерживайте эту таблицу, регулярно удаляя элементы, которые старше максимального срока жизни любого токена.

person The Tahaan    schedule 21.02.2019

Прежде всего, при использовании JWT для сеанса важно определить:

  • Токен без сохранения состояния: содержит все данные сеанса внутри токена. Таким образом, вам не нужно хранить его где-либо.
  • Steteful Token: содержит идентификатор сеанса. Когда сервер получит токен, ему нужно будет получить информацию о сеансе.

Если вы используете решение с отслеживанием состояния, чтобы сделать токен недействительным, вы можете запросить в БД идентификатор последнего сеанса для этого пользователя и сравнить с полученным токеном.
В решении без сохранения состояния, как указывалось в других ответах, вы будете вероятно, нужно где-то сохранить состояние. Но по сравнению с Stateful Token вам нужно хранить только счетчик, как вы предложили, или «last_login», как предложил @The Tahaan.

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

  • Пользователь входит в систему на другом устройстве.
  • Прошло определенное время.
person victortv    schedule 21.02.2019

Как я думаю это можно сделать. Просто создайте случайный идентификатор (давайте вызовем этот код проверки) и сохраните его в БД всякий раз, когда генерируется jwt. Закодируйте это в JWT. всякий раз, когда какой-либо запрос выполняется с помощью jwt, проверяйте, соответствует ли код проверки, закодированный в jwt, в БД. Если пользователь попытается войти в систему на другом устройстве, он повторно сгенерирует код проверки, истечет срок действия всех других сеансов.

person Nikhil bhatia    schedule 25.03.2019