Для нового проекта node.js, над которым я работаю, я думаю о переходе от сеансового подхода на основе файлов cookie (я имею в виду сохранение идентификатора в хранилище значений ключей, содержащем сеансы пользователя в браузере пользователя). к подходу сеанса на основе токенов (без хранения значения ключа) с использованием веб-токенов JSON (jwt).
Проект представляет собой игру, в которой используется socket.io - проведение сеанса на основе токенов было бы полезно в таком сценарии, когда в одном сеансе будет несколько каналов связи (web и socket.io).
Как обеспечить токен / недействительность сеанса с сервера, используя подход jwt?
Я также хотел понять, на какие распространенные (или необычные) подводные камни / атаки я должен обращать внимание с такой парадигмой. Например, если эта парадигма уязвима к тем же / различным видам атак, что и подход на основе хранилища сеансов / файлов cookie.
Итак, скажем, у меня есть следующее (адаптировано из этого и этого ):
Вход в магазин сессий:
app.get('/login', function(request, response) {
var user = {username: request.body.username, password: request.body.password };
// Validate somehow
validate(user, function(isValid, profile) {
// Create session token
var token= createSessionToken();
// Add to a key-value database
KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});
// The client should save this session token in a cookie
response.json({sessionToken: token});
});
}
Логин на основе токена:
var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
var user = {username: request.body.username, password: request.body.password };
// Validate somehow
validate(user, function(isValid, profile) {
var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
response.json({token: token});
});
}
-
Выход (или аннулирование) для подхода хранилища сеансов потребует обновления базы данных KeyValueStore с указанным токеном.
Похоже, такого механизма не было бы в подходе на основе токенов, поскольку сам токен содержал бы информацию, которая обычно существовала бы в хранилище значений ключей.
источник
isRevoked
опцию или попытаться повторить ту же функциональность. github.com/auth0/express-jwt#revoked-tokensОтветы:
Я тоже исследовал этот вопрос, и хотя ни одна из представленных ниже идей не является полным решением, они могут помочь другим исключить идеи или предложить дальнейшие.
1) Просто удалите токен из клиента
Очевидно, что это ничего не делает для безопасности на стороне сервера, но останавливает злоумышленника, удаляя токен из существования (т. Е. Им пришлось бы украсть токен до выхода из системы).
2) Создать черный список токенов
Вы можете хранить недействительные токены до истечения срока их действия и сравнивать их с входящими запросами. По-видимому, это сводит на нет причину, по которой, прежде всего, необходимо использовать полностью токен, поскольку вам нужно будет обращаться к базе данных для каждого запроса. Размер хранилища, вероятно, будет ниже, поскольку вам нужно будет хранить только токены, которые были между выходом из системы и временем истечения срока действия (это внутреннее чувство, и оно определенно зависит от контекста).
3) Просто держите время истечения токена коротким и часто меняйте его
Если вы сохраняете время истечения токена с достаточно короткими интервалами, а работающий клиент отслеживает и запрашивает обновления, когда это необходимо, номер 1 будет эффективно работать как полная система выхода из системы. Проблема этого метода в том, что он не позволяет пользователю войти в систему между закрытиями клиентского кода (в зависимости от того, как долго вы делаете интервал истечения).
Планы действий в чрезвычайных ситуациях
Если когда-либо возникла чрезвычайная ситуация или маркер пользователя был скомпрометирован, вы могли бы разрешить пользователю изменить базовый идентификатор поиска пользователя с помощью своих учетных данных для входа. Это сделает все связанные токены недействительными, так как связанный пользователь больше не сможет быть найден.
Я также хотел отметить, что это хорошая идея, чтобы включить в маркер дату последнего входа в систему, чтобы вы могли принудительно выполнить повторный вход через некоторый отдаленный период времени.
С точки зрения сходства / различий в отношении атак с использованием токенов, этот пост посвящен вопросу: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown
источник
2)
выше. Хотя он работает нормально, лично я не вижу большой разницы с традиционными сессионными магазинами. Я предполагаю, что требования к хранилищу будут ниже, но вам все еще нужна база данных. Самым большим призывом JWT для меня было вообще не использовать базу данных для сессий.Приведенные выше идеи хороши, но очень простой и легкий способ аннулировать все существующие JWT - просто изменить секрет.
Если ваш сервер создает JWT, подписывает его секретом (JWS), а затем отправляет его клиенту, простое изменение секрета приведет к аннулированию всех существующих токенов и потребует от всех пользователей получения нового токена для аутентификации, поскольку их старый токен внезапно становится недействительным согласно на сервер.
Он не требует каких-либо модификаций для реального содержимого токена (или идентификатора поиска).
Понятно, что это работает только в экстренном случае, когда вы хотите, чтобы истек срок действия всех существующих токенов, для каждого срока действия токена требуется одно из указанных выше решений (например, короткое время истечения токена или аннулирование сохраненного ключа внутри токена).
источник
Это в первую очередь длинный комментарий, поддерживающий и основывающийся на ответе @mattway
Данный:
Некоторые из других предлагаемых решений на этой странице рекомендуют использовать хранилище данных при каждом запросе. Если вы нажмете на главное хранилище данных для проверки каждого запроса на аутентификацию, то я вижу меньше причин использовать JWT вместо других установленных механизмов аутентификации токена. По сути, вы сделали JWT с состоянием, а не с состоянием, если каждый раз заходите в хранилище данных.
(Если ваш сайт получает большое количество неавторизованных запросов, то JWT отклонит их, не обращаясь к хранилищу данных, что полезно. Возможно, есть и другие варианты использования, подобные этому.)
Данный:
По-настоящему аутентификация JWT без сохранения состояния не может быть достигнута для типичного реального веб-приложения, поскольку JWT без сохранения состояния не имеет способа обеспечить немедленную и безопасную поддержку для следующих важных случаев использования:
Учетная запись пользователя удалена / заблокирована / приостановлена.
Пароль пользователя изменен.
Роли или разрешения пользователя изменены.
Пользователь вышел из системы администратором.
Любые другие критически важные данные приложения в токене JWT изменяются администратором сайта.
В этих случаях вы не можете дождаться истечения срока действия токена. Аннулирование токена должно произойти немедленно. Кроме того, вы не можете доверять клиенту не хранить и не использовать копию старого токена со злым умыслом или без него.
Поэтому: я думаю, что ответ от @ matt-way, # 2 TokenBlackList, был бы наиболее эффективным способом добавить требуемое состояние к аутентификации на основе JWT.
У вас есть черный список, в котором хранятся эти токены до истечения срока их действия. Список токенов будет довольно небольшим по сравнению с общим числом пользователей, поскольку он должен хранить только черные списки токенов до истечения срока их действия. Я бы реализовал это, поместив недействительные токены в redis, memcached или другое хранилище данных в памяти, которое поддерживает установку времени истечения для ключа.
Вы по-прежнему должны вызывать свою базу данных в памяти для каждого запроса аутентификации, который проходит начальную аутентификацию JWT, но вам не нужно хранить там ключи для всего вашего набора пользователей. (Что может иметь или не иметь большого значения для данного сайта.)
источник
If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
Я бы вел запись номера версии jwt в пользовательской модели. Новые токены jwt установят свою версию на это.
Когда вы проверяете jwt, просто убедитесь, что он имеет номер версии, равный текущей версии jwt пользователей.
В любое время, когда вы хотите сделать недействительными старые jwts, просто увеличьте номер версии jwt пользователей.
источник
Еще не пробовал, и он использует много информации, основанной на некоторых других ответах. Сложность здесь заключается в том, чтобы избежать обращения к хранилищу данных на стороне сервера при каждом запросе информации о пользователе. Большинство других решений требуют поиска в БД на запрос к хранилищу пользовательских сеансов. Это хорошо в определенных сценариях, но это было сделано в попытке избежать таких вызовов и сделать все необходимое состояние на стороне сервера очень маленьким. В конечном итоге вы воссоздадите сеанс на стороне сервера, хотя и небольшой, чтобы обеспечить все функции принудительной аннулирования. Но если вы хотите сделать это вот суть:
Цели:
Решение:
Это требует от вас поддерживать черный список (состояние) на сервере, предполагая, что таблица пользователя содержит запрещенную информацию о пользователе. Недопустимый черный список сеансов - это список идентификаторов пользователей. Этот черный список проверяется только во время запроса токена обновления. Записи должны жить на нем до тех пор, пока не будет обновлен токен TTL. По истечении срока действия маркера обновления пользователь должен будет снова войти в систему.
Минусы:
Плюсы:
При таком решении хранилище данных в памяти, такое как reddis, не требуется, по крайней мере, не для информации о пользователях, как вы, поскольку сервер выполняет вызов БД каждые 15 минут или около того. При использовании reddis сохранение действительного / недействительного списка сеансов будет очень быстрым и простым решением. Нет необходимости в обновлении токена. Каждый токен аутентификации будет иметь идентификатор сеанса и идентификатор устройства, они могут быть сохранены в таблице reddis при создании и аннулированы в случае необходимости. Затем они будут проверяться при каждом запросе и отклоняться при недействительности.
источник
Подход, который я рассматривал, заключается в том, чтобы всегда иметь
iat
(выпущенное в) значение в JWT. Затем, когда пользователь выходит из системы, сохраните эту метку времени в записи пользователя. При проверке JWT просто сравните отметкуiat
времени последнего выхода из системы. Еслиiat
он старше, то он не действителен. Да, вы должны пойти в БД, но я все равно буду извлекать пользовательскую запись, если JWT в противном случае действителен.Основной недостаток, который я вижу в этом, заключается в том, что он будет выводить их из всех сеансов, если они находятся в нескольких браузерах или имеют мобильный клиент.
Это также может быть хорошим механизмом для аннулирования всех JWT в системе. Часть проверки может быть связана с глобальной меткой времени последнего действительного
iat
времени.источник
token_valid_after
или что-то в этом роде. Потрясающие!Я немного опоздал, но думаю, у меня есть достойное решение.
В моей базе данных есть столбец «last_password_change», в котором хранятся дата и время последнего изменения пароля. Я также храню дату / время выпуска в JWT. При проверке токена я проверяю, был ли пароль изменен после того, как токен был выпущен, и был ли он токен отклонен, даже если срок его действия еще не истек.
источник
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Вы можете иметь поле «last_key_used» в вашей БД в документе / записи вашего пользователя.
Когда пользователь входит в систему с помощью user и pass, генерирует новую случайную строку, сохраняет ее в поле last_key_used и добавляет ее в полезную нагрузку при подписании токена.
Когда пользователь входит в систему с помощью токена, проверьте last_key_used в БД, чтобы он соответствовал номеру в токене.
Затем, когда пользователь выходит из системы, например, или если вы хотите аннулировать токен, просто измените это поле «last_key_used» на другое случайное значение, и любые последующие проверки не пройдут, что вынудит пользователя войти в систему с пользователем и снова пройти.
источник
Храните список в памяти, как это
Если срок действия ваших токенов истекает через одну неделю, то очистите или проигнорируйте записи старше этого. Также ведите только самые последние записи каждого пользователя. Размер списка будет зависеть от того, как долго вы храните свои токены и как часто пользователи отзывают свои токены. Используйте БД только тогда, когда таблица меняется. Загрузите таблицу в память при запуске приложения.
источник
------------------------ Немного опоздал на этот ответ, но может быть, он кому-то поможет ------------- -----------
На стороне клиента самый простой способ - удалить токен из хранилища браузера.
Но что делать, если вы хотите уничтожить токен на сервере Node -
Проблема с пакетом JWT заключается в том, что он не предоставляет какого-либо метода или способа уничтожения токена. Вы можете использовать различные методы в отношении JWT, которые упомянуты выше. Но здесь я иду с JWT-Redis.
Таким образом, чтобы уничтожить токен на стороне сервера, вы можете использовать пакет JWT-Redis вместо JWT
Эта библиотека (jwt-redis) полностью повторяет всю функциональность библиотеки jsonwebtoken с одним важным дополнением. Jwt-redis позволяет хранить метку токена в redis для проверки действительности. Отсутствие метки токена в redis делает токен недействительным. Чтобы уничтожить токен в jwt-redis, есть метод уничтожения
это работает следующим образом:
1) Установите jwt-redis из npm
2) Создать -
3) Для проверки -
4) Разрушить -
Примечание : вы можете указать expiresIn во время входа в токен так же, как это предусмотрено в JWT.
Может быть, это кому-то поможет
источник
Почему бы просто не использовать утверждение jti (nonce) и сохранить его в списке как поле записи пользователя (зависит от БД, но, по крайней мере, список через запятую подходит)? Нет необходимости в отдельном поиске, так как другие указали, что, по-видимому, вы все равно хотите получить запись пользователя, и таким образом вы можете иметь несколько действительных токенов для разных клиентских экземпляров («везде выйти» может сбросить список до пустого)
источник
Для проверки токена сначала проверьте время истечения токена, а затем черный список, если токен не истек.
Для длительных сеансов должен существовать механизм продления срока действия токена.
источник
Поздно к вечеринке МОИ два цента даны ниже после некоторого исследования. Во время выхода из системы убедитесь, что происходят следующие вещи ...
Очистить хранилище / сеанс клиента
Обновляйте таблицу времени последнего входа пользователя в систему и дату и время выхода из системы всякий раз, когда происходит вход или выход из системы соответственно. Таким образом, время входа в систему всегда должно быть больше, чем выход из системы (или оставить дату выхода из системы нулевой, если текущий статус - вход в систему и еще не выполнен выход из системы).
Это намного проще, чем хранить дополнительную таблицу в черном списке и регулярно очищать. Для поддержки нескольких устройств требуется дополнительная таблица для входа в систему, даты выхода с некоторыми дополнительными сведениями, такими как сведения об ОС или клиенте.
источник
Уникальная для пользователя строка и глобальная строка хешируются вместе
служить секретной частью JWT, позволяющей аннулировать как индивидуальные, так и глобальные токены. Максимальная гибкость за счет поиска / чтения БД при авторизации запроса. Также легко кешировать, так как они редко меняются.Вот пример:
пример использования см. https://jwt.io (не уверен, что они обрабатывают динамические 256-битные секреты)
источник
Я сделал это следующим образом:
unique hash
, а затем сохраните его в Redis и вашем JWT . Это можно назвать сессиейПоэтому, когда пользователь входит в систему, создается уникальный хэш, который сохраняется в Redis и внедряется в ваш JWT .
Когда пользователь пытается посетить защищенную конечную точку, вы извлекаете уникальный хэш сеанса из вашего JWT , запрашиваете повторный запрос и смотрите, соответствует ли он!
Мы можем расширить это и сделать наш JWT еще более безопасным, вот как:
Каждый X запрашивает определенный JWT , мы генерируем новый уникальный сеанс, сохраняем его в нашем JWT , а затем помещаем в черный список предыдущий.
Это означает, что JWT постоянно меняется и останавливает несанкционированный взлом JWT , кражу или что-то еще.
источник
aud
и сjti
претензиями в JWT, вы на правильном пути.Если вы хотите иметь возможность отзывать пользовательские токены, вы можете отслеживать все выпущенные токены в вашей БД и проверять, действительны ли они (существуют) в сессионной таблице. Недостатком является то, что вы будете нажимать на БД при каждом запросе.
Я не пробовал, но я предлагаю следующий метод, чтобы разрешить отзыв токена, сохраняя при этом количество обращений к БД к минимуму -
Чтобы снизить частоту проверки базы данных, разделите все выпущенные токены JWT на X групп в соответствии с некоторой детерминистической ассоциацией (например, 10 групп по первой цифре идентификатора пользователя).
Каждый токен JWT будет содержать идентификатор группы и метку времени, созданную при создании токена. например,
{ "group_id": 1, "timestamp": 1551861473716 }
Сервер будет хранить все идентификаторы групп в памяти, и каждая группа будет иметь временную метку, которая указывает, когда было последним событием выхода пользователя из этой группы. например,
{ "group1": 1551861473714, "group2": 1551861487293, ... }
Запросы с токеном JWT, которые имеют более старую групповую временную метку, будут проверены на достоверность (попадание в БД), и, если он действителен, будет выдан новый токен JWT со свежей временной меткой для будущего использования клиентом. Если временная метка группы токена новее, мы доверяем JWT (без попадания в БД).
Так -
источник
Если опция «Выйти со всех устройств» приемлема (в большинстве случаев это так):
В любом случае для получения пользовательской записи в любом случае требуется отключение базы данных, так что это не увеличивает накладных расходов на процесс проверки. В отличие от ведения черного списка, где загрузка БД значительна из-за необходимости использовать объединение или отдельный вызов, чистить старые записи и так далее.
источник
Я собираюсь ответить, если нам нужно обеспечить выход из всех устройств, когда мы используем JWT. Этот подход будет использовать поиск в базе данных для каждого запроса. Потому что нам нужно постоянное состояние безопасности, даже если происходит сбой сервера. В пользовательской таблице у нас будет два столбца
Всякий раз, когда от пользователя поступает запрос на выход, мы обновляем LastValidTime до текущего времени, а Logged-In - до false. Если есть запрос на вход в систему, мы не будем менять LastValidTime, но для Logged-In будет установлено значение true.
Когда мы создаем JWT, у нас будет время создания JWT в полезной нагрузке. Когда мы авторизируемся на услугу, мы проверим 3 условия
Давайте посмотрим на практический сценарий.
У пользователя X есть два устройства A, B. Он зашел на наш сервер в 19:00, используя устройство A и устройство B. (допустим, время истечения JWT составляет 12 часов). A и B оба имеют JWT с созданным временем: 7 вечера
В 9 часов вечера он потерял свое устройство B. Он немедленно вышел из системы с устройства A. Это означает, что теперь в нашей пользовательской записи базы данных X для LastValidTime задано значение «ThatDate: 9: 00: xx: xxx», а для входа в систему - «false».
В 9:30 Mr.Thief пытается войти с помощью устройства B. Мы проверим базу данных, даже если залогинен неверно, поэтому мы не позволим.
В 10 часов вечера Mr.X входит в систему со своего устройства A. Теперь устройство A имеет JWT с созданным временем: 22:00. Теперь для базы данных Logged-In установлено значение «true»
В 22:30 Mr.Thief пытается войти в систему. Несмотря на то, что залогинен - это правда. LastValidTime находится в базе данных в 9 вечера, но JWT B создал время как 7 вечера. Поэтому ему не разрешат получить доступ к услуге. Таким образом, используя устройство B без пароля, он не может использовать уже созданный JWT после выхода из одного устройства.
источник
Решение IAM, такое как Keycloak (над которым я работал), обеспечивает конечную точку отзыва токена, например
Конечная точка отзыва токена
/realms/{realm-name}/protocol/openid-connect/revoke
Если вы просто хотите выйти из системы с помощью пользовательского агента (или пользователя), вы также можете вызвать конечную точку (это просто сделает недействительными токены). Опять же, в случае Keycloak, проверяющая сторона просто должна вызвать конечную точку
/realms/{realm-name}/protocol/openid-connect/logout
Ссылка на случай, если вы хотите узнать больше
источник
Это кажется действительно трудным решить без поиска БД при каждой проверке токена. Альтернатива, о которой я могу подумать, - это сохранение черного списка недействительных токенов на стороне сервера; который должен обновляться в базе данных всякий раз, когда происходит какое-либо изменение, чтобы сохранить изменения при перезапусках, заставляя сервер проверять базу данных при перезапуске, чтобы загрузить текущий черный список.
Но если вы храните его в памяти сервера (своего рода глобальная переменная), он не будет масштабируемым на нескольких серверах, если вы используете более одного, поэтому в этом случае вы можете хранить его в общем кэше Redis, что должно быть настройка для сохранения данных где-нибудь (база данных? файловая система?) на случай, если их нужно будет перезапустить, и каждый раз, когда запускается новый сервер, он должен подписаться на кэш Redis.
В качестве альтернативы черному списку, используя то же решение, вы можете сделать это с хешем, сохраненным в redis для сеанса, как указывает этот другой ответ (хотя не уверен, что это будет более эффективно при входе многих пользователей).
Это звучит ужасно сложно? это для меня!
Отказ от ответственности: я не использовал Redis.
источник
Если вы используете axios или аналогичную библиотеку http-запросов на основе обещаний, вы можете просто уничтожить токен во внешнем интерфейсе
.then()
детали. Он будет запущен в ответной части .then () после того, как пользователь выполнит эту функцию (код результата с конечной точки сервера должен быть в порядке, 200). После того, как пользователь щелкнет этот маршрут при поиске данных, если поле базы данныхuser_enabled
имеет значение false, это приведет к уничтожению токена, и пользователь сразу же выйдет из системы и прекратит доступ к защищенным маршрутам / страницам. Нам не нужно ждать окончания срока действия токена, пока пользователь постоянно находится в системе.источник
Я просто сохраняю токен в таблице пользователей, когда при входе в систему я обновляю новый токен, а когда auth равен текущему пользователю jwt.
Я думаю, что это не лучшее решение, но это работает для меня.
источник
Stateless JWT
иStateful JWT
(что очень похоже на сеансы).Stateful JWT
может извлечь выгоду из поддержания белого списка токенов.