Недействительный токен JSON

Для нового проекта node.js, над которым я работаю, я думаю о переключении с подхода, основанного на cookie-сеансе (я имею в виду, что он хранит идентификатор в хранилище ключей, содержащих пользовательские сеансы в пользовательском браузере) к подходу на основе токенов (без сохранения значения ключа) с использованием JSON Web Tokens (jwt).

Проект - это игра, в которой используется socket.io - наличие сеанса на основе токенов было бы полезно в таком сценарии, где в течение одного сеанса будет использоваться несколько каналов связи (web и socket.io)

Как бы обеспечить аннулирование токена/сеанса с сервера с помощью подхода jwt?

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

Итак, скажем, у меня есть следующее (адаптировано из this и this):

Вход в магазин сессий:

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 базы данных с указанным токеном.

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

Ответ 1

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

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

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

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

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

3) Просто соблюдайте время истечения срока действия токена и часто вращайте его

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

Планы действий при непредвиденных обстоятельствах

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

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

С точки зрения сходства/различий в отношении атак с использованием токенов, в этом сообщении рассматривается вопрос: http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

Ответ 2

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

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

Он не требует каких-либо изменений в фактическом содержимом токена (или идентификаторе поиска).

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

Ответ 3

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

Учитывая:

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

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

Дано:

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

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

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

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

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

Цели:

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

Решение:

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

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

Минусы:

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

Плюсы:

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

В вашем БД на вашем пользовательском документе/записи может быть поле "last_key_used".

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

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

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

Ответ 9

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

Ответ 10

  • Дайте 1-дневный срок действия токенов
  • Сохраняйте ежедневный черный список.
  • Поместите маркеры недействительных/логарифмов в черный список

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

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

Ответ 11

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

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

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

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

Ответ 12

Сохраните список в памяти, подобный этому

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

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

Ответ 13

Уникальная для каждой пользовательской строки и глобальная строка, хэшированная вместе

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

Ответ 14

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

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

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

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

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

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

Это означает, что JWT постоянно меняется и останавливает устаревшее JWT, взломанное, украденное или что-то еще.

Ответ 15

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

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

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

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

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

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

Так -

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

Ответ 16

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

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

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

Ответ 17

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

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

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

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

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

Ответ 18

------------------------Bit опоздал на этот ответ, но, возможно, он поможет someone------------------------

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

Но что делать, если вы хотите уничтожить токен на сервере 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.

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

Ответ 19

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

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

Ответ 20

что если вы просто сгенерируете новый токен с сервера с expiresIn: 0 и вернете его клиенту и сохраните его в файле cookie?