Аннулирование веб-токенов JSON

421

Для нового проекта 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 с указанным токеном.

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

funseiki
источник
1
Если вы используете пакет 'express-jwt', вы можете взглянуть на эту isRevokedопцию или попытаться повторить ту же функциональность. github.com/auth0/express-jwt#revoked-tokens
Signus
1
Подумайте об использовании короткого времени истечения для токена доступа и использования токена обновления с долгим сроком действия, чтобы можно было проверять состояние доступа пользователя в базе данных (черные списки). auth0.com/blog/…
Rohmer
другой вариант - это добавление IP-адреса в полезную нагрузку при создании токена jwt и проверка сохраненного IP-адреса в сравнении с входящим запросом на тот же IP-адрес. например: req.connection.remoteAddress в nodeJs. Есть провайдеры, которые не выдают статический IP для каждого клиента, я думаю, что это не будет проблемой, если клиент не подключится к Интернету.
Гихан Сандару

Ответы:

392

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

1) Просто удалите токен из клиента

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

2) Создать черный список токенов

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

3) Просто держите время истечения токена коротким и часто меняйте его

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

Планы действий в чрезвычайных ситуациях

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

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

С точки зрения сходства / различий в отношении атак с использованием токенов, этот пост посвящен вопросу: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown

Мэтт Уэй
источник
3
Отличный подход. Моя интуиция заключается в том, чтобы делать комбинацию всех 3 и / или запрашивать новый токен после каждого «n» запросов (в отличие от таймера). Мы используем redis для хранения объектов в памяти, и мы могли бы легко использовать это для случая # 2, и тогда задержка пошла бы ПУТЬ.
Аарон Вагнер
2
Этот пост ужаса кодирования предлагает несколько советов: держите cookie-файлы с сессиями (или токены) короткими, но делайте их невидимыми для пользователя - что соответствует # 3. Моя собственная интуиция (возможно, потому что она более традиционна) состоит в том, чтобы токен (или его хэш) выступал в качестве ключа в базе данных сеансов, занесенных в белый список (аналогично # 2)
funseiki
8
Статья хорошо написана, и является разработанной версией 2)выше. Хотя он работает нормально, лично я не вижу большой разницы с традиционными сессионными магазинами. Я предполагаю, что требования к хранилищу будут ниже, но вам все еще нужна база данных. Самым большим призывом JWT для меня было вообще не использовать базу данных для сессий.
Мэтт Уэй
211
Обычный подход для аннулирования токенов, когда пользователь меняет свой пароль, состоит в том, чтобы подписать токен хешем своего пароля. Таким образом, если пароль изменяется, любые предыдущие токены автоматически не проходят проверку. Вы можете расширить это до выхода из системы, включив время последнего выхода из системы в записи пользователя и используя комбинацию времени последнего выхода из системы и хэша пароля для подписи токена. Это требует поиска БД каждый раз, когда вам нужно проверить подпись токена, но, по-видимому, вы все равно ищете пользователя.
Трэвис Терри
4
Черный список можно сделать эффективным, если он хранится в памяти, поэтому к БД нужно обращаться только для записи недействительных записей, удаления недействительных с истекшим сроком действия и чтения только при запуске сервера. В архитектуре с балансировкой нагрузки черный список в памяти может опрашивать БД через короткие промежутки времени, например 10 с, ограничивая доступ к недействительным токенам. Эти подходы позволяют серверу продолжать аутентификацию запросов без доступа к базе данных по запросу.
Джо Лапп
86

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

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

Он не требует каких-либо модификаций для реального содержимого токена (или идентификатора поиска).

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

Энди
источник
9
Я думаю, что этот подход не идеален. Хотя это работает и, конечно, очень просто, представьте себе случай, когда вы используете открытый ключ - вы не захотите переходить и создавать этот ключ заново всякий раз, когда хотите аннулировать один токен.
Signus
1
@KijanaWoodard, пара открытого / секретного ключей может использоваться для проверки подлинности подписи как эффективного секрета в алгоритме RS256. В показанном здесь примере он упоминает об изменении секрета, чтобы сделать недействительным JWT. Это может быть сделано либо a) введением поддельного pubkey, который не совпадает с подписью, либо b) генерацией нового pubkey. В этой ситуации это не идеально.
Signus
1
@Signus - понял. не используя открытый ключ в качестве секрета, но другие могут полагаться на открытый ключ для проверки подписи.
Киджана Вудард,
8
Это очень плохое решение. Основная причина использования JWT - это отсутствие состояния и масштабирование. Использование динамического секрета вводит состояние. Если служба кластеризована на нескольких узлах, вам придется синхронизировать секрет каждый раз, когда выдается новый токен. Вам придется хранить секреты в базе данных или другой внешней службе, которая будет просто заново изобретать аутентификацию на основе
Туомас Тойвонен,
5
@TuomasToivonen, но вы должны подписать JWT с секретом и иметь возможность проверить JWT с тем же секретом. Таким образом, вы должны хранить секрет на защищенных ресурсах. Если секрет скомпрометирован, вы должны изменить его и распространить это изменение на каждый из ваших узлов. Хостинг-провайдеры с кластеризацией / масштабированием обычно позволяют хранить секреты в их службе, чтобы сделать распространение этих секретов простым и надежным.
Rohmer
67

Это в первую очередь длинный комментарий, поддерживающий и основывающийся на ответе @mattway

Данный:

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

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

Данный:

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

Учетная запись пользователя удалена / заблокирована / приостановлена.

Пароль пользователя изменен.

Роли или разрешения пользователя изменены.

Пользователь вышел из системы администратором.

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

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

Поэтому: я думаю, что ответ от @ matt-way, # 2 TokenBlackList, был бы наиболее эффективным способом добавить требуемое состояние к аутентификации на основе JWT.

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

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

Эд Дж
источник
15
Я не согласен с вашим ответом. Попадание в базу данных не делает ничего сохраняющим состояние; сохранение состояния на вашем бэкэнде делает. JWT не был создан, чтобы вам не приходилось обращаться к базе данных при каждом запросе. Каждое основное приложение, использующее JWT, поддерживается базой данных. JWT решает совершенно другую проблему. en.wikipedia.org/wiki/Stateless_protocol
Джулиан,
6
@ Джулиан, ты не мог бы рассказать об этом немного? Какую проблему JWT действительно решает тогда?
zero01alpha
8
@ zero01alpha Authentication: это наиболее распространенный сценарий использования JWT. Как только пользователь вошел в систему, каждый последующий запрос будет включать JWT, позволяющий пользователю получать доступ к маршрутам, службам и ресурсам, которые разрешены с этим токеном. Обмен информацией: веб-токены JSON являются хорошим способом безопасной передачи информации между сторонами. Поскольку JWT могут быть подписаны, вы можете быть уверены, что отправители являются теми, кем они себя называют. См. Jwt.io/introduction
Джулиан,
7
@Julian Я не согласен с вашим несогласием :) JWT решает проблему (для служб), чтобы иметь доступ к централизованному объекту, предоставляющему информацию авторизации для любого конкретного клиента. Таким образом, вместо службы A и службы B необходимо получить доступ к некоторому ресурсу, чтобы выяснить, имеет ли клиент X полномочия на выполнение каких-либо действий, а службы A и B получают токен от X, который подтверждает его / ее разрешения (чаще всего выданные третьим лицом). партия). В любом случае, JWT - это инструмент, который помогает избежать общего состояния между службами в системе, особенно когда ими управляет более одного поставщика услуг.
Ливанов
1
Также из jwt.io/introduction 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.
Гигантский
43

Я бы вел запись номера версии jwt в пользовательской модели. Новые токены jwt установят свою версию на это.

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

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

DaftMonk
источник
15
Это интересная идея, единственное, где хранить версию, так как частью назначения токенов является отсутствие состояния и отсутствие необходимости использования базы данных. Жестко закодированная версия усложнит задачу, а номер версии в базе данных сведет на нет некоторые преимущества использования токенов.
Стивен Смит
13
Предположительно, вы уже сохраняете идентификатор пользователя в своем токене, а затем запрашиваете базу данных, чтобы убедиться, что пользователь существует / авторизован для доступа к конечной точке API. Таким образом, вы не выполняете никаких дополнительных запросов к базе данных, сравнивая номер версии токена jwt с номером пользователя.
DaftMonk
5
Я не должен говорить предположительно, потому что есть много ситуаций, когда вы можете использовать токены с проверками, которые вообще не затрагивают базу данных. Но я думаю, что в этом случае его трудно избежать.
DaftMonk
11
Что делать, если пользователь входит в систему с нескольких устройств? Нужно ли использовать один токен для всех или вход в систему делает недействительными все предыдущие?
meeDamian
10
Я согласен с @SergioCorrea. Это сделало бы JWT почти таким же состоянием, как и любой другой механизм аутентификации токена.
Эд Дж
40

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

Цели:

  • Смягчение использования хранилища данных (без учета состояния).
  • Возможность принудительного выхода из системы всех пользователей.
  • Возможность принудительного выхода любого человека в любое время.
  • Возможность требовать повторного ввода пароля через определенное время.
  • Возможность работы с несколькими клиентами.
  • Возможность принудительного повторного входа в систему, когда пользователь нажимает кнопку выхода из определенного клиента. (Чтобы предотвратить «удаление» клиентского токена после ухода пользователя - см. Комментарии для дополнительной информации)

Решение:

  • Используйте токены доступа с коротким сроком действия (<5 м) в сочетании с хранившимся у клиента обновленным токеном обновления (несколько часов) .
  • Каждый запрос проверяет срок действия токена авторизации или обновления.
  • Когда срок действия маркера доступа истекает, клиент использует токен обновления для обновления токена доступа.
  • Во время проверки маркера обновления сервер проверяет небольшой черный список идентификаторов пользователей - если он найден, отклонить запрос на обновление.
  • Если у клиента нет действительного (не просроченного) обновления или токена авторизации, пользователь должен снова войти в систему, поскольку все остальные запросы будут отклонены.
  • При входе в систему проверьте хранилище пользовательских данных на бан.
  • При выходе из системы - добавьте этого пользователя в черный список сеансов, чтобы он мог снова войти в систему. Вам потребуется хранить дополнительную информацию, чтобы не выходить из системы со всех устройств в среде с несколькими устройствами, но это можно сделать, добавив поле устройства в черный список пользователей.
  • Для принудительного повторного входа через x времени - сохраните дату последнего входа в систему в токене авторизации и проверяйте ее по запросу.
  • Для принудительного выхода из системы всех пользователей - сбросить ключ хеша токена.

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

Минусы:

  • По-прежнему требуется выполнить поиск в хранилище данных по запросу обновления токена.
  • Неверные токены могут продолжать работать для TTL токена доступа.

Плюсы:

  • Обеспечивает желаемую функциональность.
  • Действие обновления токена скрыто от пользователя при нормальной работе.
  • Требуется только выполнять поиск в хранилище данных по запросам на обновление вместо каждого запроса. т.е. 1 раз в 15 минут вместо 1 в секунду.
  • Минимизирует состояние на стороне сервера до очень маленького черного списка.

При таком решении хранилище данных в памяти, такое как reddis, не требуется, по крайней мере, не для информации о пользователях, как вы, поскольку сервер выполняет вызов БД каждые 15 минут или около того. При использовании reddis сохранение действительного / недействительного списка сеансов будет очень быстрым и простым решением. Нет необходимости в обновлении токена. Каждый токен аутентификации будет иметь идентификатор сеанса и идентификатор устройства, они могут быть сохранены в таблице reddis при создании и аннулированы в случае необходимости. Затем они будут проверяться при каждом запросе и отклоняться при недействительности.

Ashtonian
источник
Как насчет сценария, когда один человек встает с компьютера, чтобы позволить другому человеку использовать тот же компьютер? Первый человек выйдет из системы и ожидает, что выход немедленно заблокирует второго человека. Если второй человек является обычным пользователем, клиент может легко заблокировать пользователя, удалив токен. Но если у второго пользователя есть навыки хакерства, у него есть время, чтобы восстановить все еще действующий токен для аутентификации как первого пользователя. Кажется, что нет никакого способа избежать необходимости немедленно аннулировать токены без промедления.
Джо Лапп
5
Или вы можете удалить JWT из sesion / локального хранилища или файла cookie.
Камиль Келчевски
1
Спасибо @Ashtonian. Проведя обширные исследования, я отказался от JWT. Если вы не приложите чрезмерных усилий для защиты секретного ключа или если вы не делегируете безопасную реализацию OAuth, JWT гораздо более уязвимы, чем обычные сеансы. Смотрите мой полный отчет: by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Джо Лапп
2
Использование маркера обновления является ключом к разрешению внесения в черный список. Отличное объяснение: auth0.com/blog/...
Ромер
1
Это кажется мне лучшим ответом, поскольку он сочетает в себе токен недолговечного доступа с токеном долгоживущего обновления, который можно внести в черный список. При выходе из системы клиент должен удалить токен доступа, чтобы 2-й пользователь не мог получить доступ (даже если токен доступа останется действительным еще несколько минут после выхода из системы). @Joe Lapp говорит, что хакер (второй пользователь) получает токен доступа даже после его удаления. Как?
M3RS
14

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

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

Это также может быть хорошим механизмом для аннулирования всех JWT в системе. Часть проверки может быть связана с глобальной меткой времени последнего действительного iatвремени.

Брак Мо
источник
1
Хорошая мысль! Чтобы решить проблему «одного устройства», нужно сделать эту функцию не аварийной, а выходить из системы. Сохраните дату в пользовательской записи, которая делает недействительными все токены, выпущенные до нее. Нечто подобное token_valid_afterили что-то в этом роде. Потрясающие!
OneHoopyFrood
1
Привет @OneHoopyFrood у вас есть пример кода, чтобы помочь мне понять идею лучше? Я очень ценю вашу помощь!
alexventuraio
2
Как и все другие предлагаемые решения, это требует поиска в базе данных, поэтому этот вопрос существует, потому что избегание этого поиска - самая важная вещь здесь! (Производительность, масштабируемость). При обычных обстоятельствах вам не нужен просмотр БД для получения пользовательских данных, вы уже получили их от клиента.
Роб Эванс
9

Я немного опоздал, но думаю, у меня есть достойное решение.

В моей базе данных есть столбец «last_password_change», в котором хранятся дата и время последнего изменения пароля. Я также храню дату / время выпуска в JWT. При проверке токена я проверяю, был ли пароль изменен после того, как токен был выпущен, и был ли он токен отклонен, даже если срок его действия еще не истек.

Матас Кайрайтис
источник
1
Как вы отклоняете токен? Можете ли вы показать краткий пример кода?
alexventuraio
1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Вануан
15
Требуется поиск в БД!
Роб Эванс
5

Вы можете иметь поле «last_key_used» в вашей БД в документе / записи вашего пользователя.

Когда пользователь входит в систему с помощью user и pass, генерирует новую случайную строку, сохраняет ее в поле last_key_used и добавляет ее в полезную нагрузку при подписании токена.

Когда пользователь входит в систему с помощью токена, проверьте last_key_used в БД, чтобы он соответствовал номеру в токене.

Затем, когда пользователь выходит из системы, например, или если вы хотите аннулировать токен, просто измените это поле «last_key_used» на другое случайное значение, и любые последующие проверки не пройдут, что вынудит пользователя войти в систему с пользователем и снова пройти.

NickVarcha
источник
Это решение, которое я рассматривал, но у него есть следующие недостатки: (1) вы либо выполняете поиск в базе данных для каждого запроса, чтобы проверить случайность (сводя на нет причину использования токенов вместо сессий), либо проверять только периодически после истечения срока действия маркера обновления (предотвращая немедленный выход пользователей из системы или немедленное прекращение сеансов); и (2) при выходе из системы пользователь выходит из всех браузеров и со всех устройств (что обычно не является ожидаемым поведением).
Джо Лапп
Вам не нужно менять ключ, когда пользователь выходит из системы, только когда он меняет свой пароль или - если вы его предоставляете - когда он
решает
3

Храните список в памяти, как это

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

Если срок действия ваших токенов истекает через одну неделю, то очистите или проигнорируйте записи старше этого. Также ведите только самые последние записи каждого пользователя. Размер списка будет зависеть от того, как долго вы храните свои токены и как часто пользователи отзывают свои токены. Используйте БД только тогда, когда таблица меняется. Загрузите таблицу в память при запуске приложения.

Эдуардо
источник
2
Большинство производственных сайтов работают на более чем одном сервере, поэтому это решение не будет работать. Добавление Redis или аналогичного промежуточного кэша значительно усложняет систему и часто приносит больше проблем, чем решений.
user2555515
@ user2555515 все серверы могут быть синхронизированы с базой данных. Это ваш выбор попадать в базу данных каждый раз или нет. Вы могли бы сказать, какие проблемы это приносит.
Эдуардо
3

------------------------ Немного опоздал на этот ответ, но может быть, он кому-то поможет ------------- -----------

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

Но что делать, если вы хотите уничтожить токен на сервере Node -

Проблема с пакетом JWT заключается в том, что он не предоставляет какого-либо метода или способа уничтожения токена. Вы можете использовать различные методы в отношении JWT, которые упомянуты выше. Но здесь я иду с JWT-Redis.

Таким образом, чтобы уничтожить токен на стороне сервера, вы можете использовать пакет JWT-Redis вместо JWT

Эта библиотека (jwt-redis) полностью повторяет всю функциональность библиотеки jsonwebtoken с одним важным дополнением. Jwt-redis позволяет хранить метку токена в redis для проверки действительности. Отсутствие метки токена в redis делает токен недействительным. Чтобы уничтожить токен в jwt-redis, есть метод уничтожения

это работает следующим образом:

1) Установите jwt-redis из npm

2) Создать -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) Для проверки -

jwtr.verify(token, secret);

4) Разрушить -

jwtr.destroy(token)

Примечание : вы можете указать expiresIn во время входа в токен так же, как это предусмотрено в JWT.

Может быть, это кому-то поможет

Аман Кумар Гупта
источник
2

Почему бы просто не использовать утверждение jti (nonce) и сохранить его в списке как поле записи пользователя (зависит от БД, но, по крайней мере, список через запятую подходит)? Нет необходимости в отдельном поиске, так как другие указали, что, по-видимому, вы все равно хотите получить запись пользователя, и таким образом вы можете иметь несколько действительных токенов для разных клиентских экземпляров («везде выйти» может сбросить список до пустого)

davidkomer
источник
Да это. Возможно, создайте отношение «один ко многим» между пользовательской таблицей и новой (сеансовой) таблицей, чтобы вы могли хранить метаданные вместе с утверждениями jti.
Питер Лада
2
  1. Дайте 1 день истечения срока действия токенов
  2. Поддерживать ежедневный черный список.
  3. Поместите недействительные токены / токены выхода в черный список

Для проверки токена сначала проверьте время истечения токена, а затем черный список, если токен не истек.

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

Эбру Йенер
источник
4
положи токены в черный список и твое безгражданство
Керем Байдоган
2

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

Очистить хранилище / сеанс клиента

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

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

Shamseer
источник
2

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

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

Вот пример:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

пример использования см. https://jwt.io (не уверен, что они обрабатывают динамические 256-битные секреты)

Марк Эссель
источник
1
Достаточно было бы более подробной информации
Giantas
2
@giantas, я думаю, что Марк имеет в виду часть подписи. Таким образом, вместо того, чтобы использовать только один ключ для подписи JWT, объедините его с ключом, уникальным для каждого клиента. Поэтому, если вы хотите аннулировать весь сеанс пользователя, просто измените ключ для этого пользователя, а если вы хотите сделать недействительным весь сеанс в вашей системе, просто измените этот глобальный единственный ключ.
Томми Ария Прадана
1

Я сделал это следующим образом:

  1. Создайте unique hash, а затем сохраните его в Redis и вашем JWT . Это можно назвать сессией
    • Мы также будем хранить количество запросов , сделанных конкретным JWT. Каждый раз, когда jwt отправляется на сервер, мы увеличиваем число запросов на целое число. (это необязательно)

Поэтому, когда пользователь входит в систему, создается уникальный хэш, который сохраняется в Redis и внедряется в ваш JWT .

Когда пользователь пытается посетить защищенную конечную точку, вы извлекаете уникальный хэш сеанса из вашего JWT , запрашиваете повторный запрос и смотрите, соответствует ли он!

Мы можем расширить это и сделать наш JWT еще более безопасным, вот как:

Каждый X запрашивает определенный JWT , мы генерируем новый уникальный сеанс, сохраняем его в нашем JWT , а затем помещаем в черный список предыдущий.

Это означает, что JWT постоянно меняется и останавливает несанкционированный взлом JWT , кражу или что-то еще.

James111
источник
1
Вы можете хэшировать сам токен и сохранять это значение в Redis, а не вводить новый хэш в токен.
Фруг
Также ознакомьтесь audи с jtiпретензиями в JWT, вы на правильном пути.
Питер Лада
1

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

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

Чтобы снизить частоту проверки базы данных, разделите все выпущенные токены JWT на X групп в соответствии с некоторой детерминистической ассоциацией (например, 10 групп по первой цифре идентификатора пользователя).

Каждый токен JWT будет содержать идентификатор группы и метку времени, созданную при создании токена. например,{ "group_id": 1, "timestamp": 1551861473716 }

Сервер будет хранить все идентификаторы групп в памяти, и каждая группа будет иметь временную метку, которая указывает, когда было последним событием выхода пользователя из этой группы. например,{ "group1": 1551861473714, "group2": 1551861487293, ... }

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

Так -

  1. Мы проверяем токен JWT с использованием БД только в том случае, если токен имеет старую временную метку группы, в то время как будущие запросы не будут проверяться до тех пор, пока кто-то из группы пользователей не выйдет из системы.
  2. Мы используем группы, чтобы ограничить количество изменений меток времени (скажем, есть пользователь, входящий и выходящий из системы, как будто завтра нет - повлияет только на ограниченное количество пользователей вместо всех)
  3. Мы ограничиваем количество групп, чтобы ограничить количество временных меток, хранящихся в памяти
  4. Отменить токен очень просто - просто удалите его из таблицы сеансов и сгенерируйте новую метку времени для группы пользователей.
Arik
источник
Тот же список может храниться в памяти (приложение для c #), и это устранит необходимость попадания в базу данных для каждого запроса. Список может быть загружен из db при запуске приложения
dvdmn
1

Если опция «Выйти со всех устройств» приемлема (в большинстве случаев это так):

  • Добавьте поле версии токена в запись пользователя.
  • Добавьте значение в этом поле к утверждениям, хранящимся в JWT.
  • Увеличивайте версию каждый раз, когда пользователь выходит из системы.
  • При проверке токена сравните его версию с версией, сохраненной в пользовательской записи, и отклоните, если она не совпадает.

В любом случае для получения пользовательской записи в любом случае требуется отключение базы данных, так что это не увеличивает накладных расходов на процесс проверки. В отличие от ведения черного списка, где загрузка БД значительна из-за необходимости использовать объединение или отдельный вызов, чистить старые записи и так далее.

user2555515
источник
0

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

  1. LastValidTime (по умолчанию: время создания)
  2. Вход в систему (по умолчанию: true)

Всякий раз, когда от пользователя поступает запрос на выход, мы обновляем LastValidTime до текущего времени, а Logged-In - до false. Если есть запрос на вход в систему, мы не будем менять LastValidTime, но для Logged-In будет установлено значение true.

Когда мы создаем JWT, у нас будет время создания JWT в полезной нагрузке. Когда мы авторизируемся на услугу, мы проверим 3 условия

  1. Действительно ли JWT
  2. Время создания полезной нагрузки JWT больше, чем User LastValidTime
  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 после выхода из одного устройства.

Tharsanan
источник
0

Решение IAM, такое как Keycloak (над которым я работал), обеспечивает конечную точку отзыва токена, например

Конечная точка отзыва токена /realms/{realm-name}/protocol/openid-connect/revoke

Если вы просто хотите выйти из системы с помощью пользовательского агента (или пользователя), вы также можете вызвать конечную точку (это просто сделает недействительными токены). Опять же, в случае Keycloak, проверяющая сторона просто должна вызвать конечную точку

/realms/{realm-name}/protocol/openid-connect/logout

Ссылка на случай, если вы хотите узнать больше

Суббу Махадеван
источник
-1

Это кажется действительно трудным решить без поиска БД при каждой проверке токена. Альтернатива, о которой я могу подумать, - это сохранение черного списка недействительных токенов на стороне сервера; который должен обновляться в базе данных всякий раз, когда происходит какое-либо изменение, чтобы сохранить изменения при перезапусках, заставляя сервер проверять базу данных при перезапуске, чтобы загрузить текущий черный список.

Но если вы храните его в памяти сервера (своего рода глобальная переменная), он не будет масштабируемым на нескольких серверах, если вы используете более одного, поэтому в этом случае вы можете хранить его в общем кэше Redis, что должно быть настройка для сохранения данных где-нибудь (база данных? файловая система?) на случай, если их нужно будет перезапустить, и каждый раз, когда запускается новый сервер, он должен подписаться на кэш Redis.

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

Это звучит ужасно сложно? это для меня!

Отказ от ответственности: я не использовал Redis.

Хосе П.В.
источник
-1

Если вы используете axios или аналогичную библиотеку http-запросов на основе обещаний, вы можете просто уничтожить токен во внешнем интерфейсе .then()детали. Он будет запущен в ответной части .then () после того, как пользователь выполнит эту функцию (код результата с конечной точки сервера должен быть в порядке, 200). После того, как пользователь щелкнет этот маршрут при поиске данных, если поле базы данных user_enabledимеет значение false, это приведет к уничтожению токена, и пользователь сразу же выйдет из системы и прекратит доступ к защищенным маршрутам / страницам. Нам не нужно ждать окончания срока действия токена, пока пользователь постоянно находится в системе.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}
Дэн
источник
-3

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

Я думаю, что это не лучшее решение, но это работает для меня.

Во Ман Кин
источник
2
Конечно это не самый лучший! Любой, имеющий доступ к БД, может легко выдать себя за любого пользователя.
user2555515
1
@ user2555515 Это решение прекрасно работает, если токен, хранящийся в базе данных, зашифрован, как и любой пароль, хранящийся в базе данных. Есть разница между Stateless JWTи Stateful JWT(что очень похоже на сеансы). Stateful JWTможет извлечь выгоду из поддержания белого списка токенов.
TheDarkIn1978