Аннулирование веб-токенов JSON
Для нового проекта 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 с указанным токеном.
Похоже, такого механизма не было бы в подходе на основе токенов, поскольку сам токен содержал бы информацию, которая обычно существовала бы в хранилище значений ключей.
35 ответов
Это действительно сложно решить без просмотра базы данных при каждой проверке токена. Альтернатива, о которой я могу думать, - это ведение черного списка недействительных токенов на стороне сервера; который должен обновляться в базе данных всякий раз, когда происходит изменение, чтобы сохранить изменения при перезапусках, заставляя сервер проверять базу данных при перезапуске для загрузки текущего черного списка.
Но если вы сохраните его в памяти сервера (своего рода глобальная переменная), тогда он не будет масштабироваться на нескольких серверах, если вы используете более одного, поэтому в этом случае вы можете сохранить его в общем кеше Redis, который должен быть настройка для сохранения данных где-нибудь (база данных? файловая система?) на случай, если ее нужно перезапустить, и каждый раз, когда запускается новый сервер, он должен подписаться на кеш Redis.
Альтернатива черному списку, используя то же решение, вы можете сделать это с хешем, сохраненным в redis для каждого сеанса, как указывает этот другой ответ (хотя не уверен, что это будет более эффективно, если многие пользователи входят в систему).
Звучит ужасно сложно? это делает для меня!
Отказ от ответственности: я не использовал Redis.
Я просто сохраняю токен в таблице пользователей, когда при входе в систему я обновляю новый токен, а когда auth равен текущему пользователю jwt.
Я думаю, что это не лучшее решение, но это работает для меня.
Если вам нужен быстрый, эффективный и элегантный
logout
функциональность для входа на основе JWT (что фактически является основной причиной аннулирования JWT) вот как: заменить текущий токен токеном, срок действия которого истекает, скажем, через 1 секунду.
Подробно:
Когда вы делаете запрос от клиента с действующим присоединенным JWT, удалите JWT, который вы храните в своем локальном хранилище (если вы сохраняете его для своей функции «запомнить меня»).
Затем, когда запрос поступает на сервер
/logout
обработчик маршрута:- Если входящий JWT действителен, создайте новый JWT, срок действия которого истекает через 1 секунду, и отправьте его обратно клиенту.
- Вы можете сделать это с помощью
redirect
ответ и токен в теле ответа. Перенаправление может быть на любой публичный маршрут (скажем./home
). Или вы можете ответить обычным ответом без перенаправления, чтобы позже выполнить желаемое перенаправление на клиенте.
Сейчас на клиенте
- Когда вы получите ответ, сохраните новый JWT в локальном хранилище, где вы только что удалили старый на шаге 1 в. Теперь при желании вы можете перенаправить на клиенте, чтобы не использовать ответ перенаправления с сервера.
- Поскольку срок действия нового токена уже истек (истечение срока было установлено через 1 секунду), любая попытка перенаправления на защищенные маршруты (с уже истекшим токеном) должна перенаправлять на страницу регистрации / входа. Вы должны сами обеспечить это поведение на маршрутах, защищенных аутентификацией сервера.
Так просто. У вас только что появилась обычная функция выхода из системы по требованию с JWT.
Да, это не делает исходный JWT недействительным. Если кто-то (злоумышленник) захватил и сохранил его до того, как он сможет использовать его до истечения срока его действия. Однако его окно возможностей сужается со временем.
Но вот вторая часть решения: очень недолговечный (10 минут?) Оригинальный JWT и более долгоживущий токен обновления, сохраненный в БД и фактически отозванный или удаленный из БД.
Или здесь можно использовать черный / белый список оригинального JWT или аналогичные подходы, упомянутые выше.
Быстрое решение может применяться для требований безопасности проигравшего. Часть токенов обновления / черный список / белый список может быть добавлена для более строгих требований безопасности.
В любом случае это более простой и расширяемый по запросу подход.
Что если вы просто сгенерируете новый токен с сервера с expiresIn: 0, вернете его клиенту и сохраните в cookie?
Если вы используете axios или аналогичную библиотеку HTTP-запросов на основе обещаний, вы можете просто уничтожить токен во внешнем интерфейсе внутри .then()
часть. Он будет запущен в части ответа.then() после того, как пользователь выполнит эту функцию (код результата с конечной точки сервера должен быть в порядке, 200). После того, как пользователь щелкнет этот маршрут при поиске данных, если поле базы данныхuser_enabled
ложно, это вызовет уничтожение токена, и пользователь немедленно выйдет из системы и не сможет получить доступ к защищенным маршрутам / страницам. Нам не нужно ждать истечения срока действия токена, пока пользователь постоянно находится в системе.
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);
});
}