Недійсні веб-маркери JSON


420

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

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


1
Якщо ви використовуєте пакунок 'express-jwt', ви можете ознайомитися з isRevokedможливістю або спробувати повторити ту саму функціональність. github.com/auth0/express-jwt#revoked-tokens
Signus

1
Подумайте про використання короткого часу закінчення терміну дії на маркер доступу та використання маркера оновлення з тривалим терміном дії, щоб дозволити перевірку стану доступу користувача до бази даних (чорний список). auth0.com/blog/…
Ромер

іншим варіантом буде приєднання IP-адреси у корисному навантаженні під час генерування jwt токена та перевірка збереженого IP-адреси проти вхідного запиту на ту саму Ip-адресу. Наприклад: req.connection.remoteAddress у nodeJs. Є провайдери провайдерів, які не видають статичний IP на клієнта, я думаю, це не буде проблемою, якщо клієнт не підключиться до Інтернету.
Джихан Сандару

Відповіді:


391

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

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

7
Стаття добре написана і є детальною версією 2)вище. Хоча це працює добре, я особисто не бачу великої різниці у традиційних магазинах сесій. Я думаю, що вимога зберігання буде нижчою, але ви все одно потребуєте бази даних. Найбільшим закликом JWT для мене було те, що він взагалі не використовував базу даних для сесій.
Метт

210
Загальний підхід до недійсності маркерів, коли користувач змінює свій пароль, - це підписати маркер з допомогою хеша свого пароля. Таким чином, якщо пароль зміниться, будь-які попередні маркери автоматично не вдасться перевірити. Ви можете розширити це до виходу, включивши час останнього виходу у запис користувача та використовуючи комбінацію хешу часу останнього виходу та пароля для підписання маркера. Для цього потрібен пошук БД кожного разу, коли вам потрібно перевірити підпис маркера, але, мабуть, ви все одно шукаєте користувача.
Тревіс Террі

4
Чорний список можна зробити ефективним, зберігаючи його в пам’яті, так що БД потрібно лише натиснути, щоб записати недійсні та видалити минулі терміни недійсності та прочитати лише при запуску сервера. У архітектурі балансування навантаження, чорний список пам'яті може запитувати БД з короткими інтервалами, як 10s, обмежуючи експозицію недійсних маркерів. Ці підходи дозволяють серверу продовжувати автентифікацію запитів без доступу до БД на запит.
Джо Лапп

85

Ідеї, розміщені вище, хороші, але дуже простий і простий спосіб визнати недійсним всі існуючі JWT - це просто змінити секрет.

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

Він не потребує жодних змін до фактичного вмісту маркера (або ідентифікатора пошуку).

Зрозуміло, що це працює лише в екстреному випадку, коли ви хотіли, щоб термін дії всіх існуючих маркерів закінчився, для закінчення терміну дії маркера потрібне одне з вищезазначених рішень (наприклад, короткий час закінчення терміну дії або недійсне збереження ключа в маркері).


9
Я думаю, що цей підхід не є ідеальним. Хоча він працює і, безумовно, простий, уявіть собі випадок, коли ви використовуєте відкритий ключ - ви не хочете переходити і відтворювати цей ключ будь-коли, коли хочете визначити недійсним один маркер.
Signus

1
@KijanaWoodard, пара публічних / приватних ключів може бути використана для перевірки підпису настільки ефективно, як секрет алгоритму RS256. У наведеному тут прикладі він згадує про зміну секрету для визнання недійсним JWT. Це можна зробити, якщо: а) ввести підроблений паблік, який не відповідає підпису, або б) створити новий. У цій ситуації це менше, ніж ідеально.
Signus

1
@Signus - gotcha. не використовуючи відкритий ключ як секретний, але інші люди можуть покластися на відкритий ключ для перевірки підпису.
Kijana Woodard

8
Це дуже погане рішення. Основна причина використання JWT - це стан без громадянства та масштаби. За допомогою динамічного секрету вводиться стан. Якщо служба кластеризована по декількох вузлах, вам доведеться синхронізувати секрет щоразу, коли буде видано новий маркер. Вам потрібно буде зберігати секрети в базі даних або в іншій зовнішній службі, яка буде лише переосмисленням аутентифікації на основі файлів cookie
Tuomas Toivonen

5
@TuomasToivonen, але ви повинні підписати JWT із секретом і мати можливість перевірити JWT із цим самим секретом. Отже, ви повинні зберігати секрет на захищених ресурсах. Якщо секрет порушений, ви повинні змінити його та поширити цю зміну на кожен із своїх вузлів. Хостинг-провайдери з кластеризацією / масштабуванням зазвичай дозволяють зберігати секрети в їх службі, щоб зробити розповсюдження цих секретів простим і надійним.
Ромер

67

Це в першу чергу довгий коментар, який підтримує та спирається на відповідь від @mattway

Подано:

Деякі з інших запропонованих рішень на цій сторінці пропонують вражати сховище даних у кожному запиті. Якщо ви натискаєте на основний сховище даних для перевірки кожного запиту аутентифікації, я бачу менше причин використовувати JWT замість інших встановлених механізмів аутентифікації токенів. Ви по суті зробили JWT видатним, замість безгромадянського стану, якщо кожного разу заходите в сховище даних.

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

Подано:

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

Обліковий запис користувача видалено / заблоковано / призупинено.

Пароль користувача змінено.

Ролі або дозволи користувача змінюються.

Користувач вийшов адміністратором.

Будь-які інші важливі дані програми в маркері JWT змінюються адміністратором сайту.

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

Тому: Я думаю, що відповідь з @ matt-way, №2 TokenBlackList, був би найбільш ефективним способом додати необхідний стан до автентифікації на основі JWT.

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

Ви все ще повинні здійснити виклик на свій db пам'яті для кожного запиту на аутентифікацію, який передає початковий autor JWT, але вам не потрібно зберігати там ключі для всього набору користувачів. (Що може бути, а може і не бути великою справою для даного сайту.)


15
Я не згоден з вашою відповіддю. Потрапляння в базу даних не робить нічого страшним; зберігає стан у своєму бекенді. JWT не створений так, що вам не доведеться потрапляти в базу даних за кожним запитом. Кожна основна програма, яка використовує JWT, підтримується базою даних. JWT вирішує зовсім іншу проблему. en.wikipedia.org/wiki/Stateless_protocol
Джуліан

6
@Julian Ви можете трохи детальніше розібратися в цьому? Яку проблему реально вирішує 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.
giantas

43

Я б записував номер версії jwt на моделі користувача. Нові маркери jwt встановили б свою версію для цього.

Коли ви перевіряєте jwt, просто перевірте, чи є у нього номер версії, який дорівнює поточній версії jwt користувачів.

Кожен раз, коли ви хочете визнати недійсним старі jwts, просто збийте номер версії jwt користувачів.


15
Це цікава ідея, єдине, де зберігати версію, оскільки це частина токенів - це без громадянства і не потрібно використовувати базу даних. Жорстко кодована версія затрудняє збивання, а номер версії в базі даних заперечує деякі переваги використання жетонів.
Стівен Сміт

13
Імовірно, ви вже зберігаєте ідентифікатор користувача у своєму маркері, а потім запитуєте базу даних, щоб перевірити, чи існує користувач / має право доступу до кінцевої точки api. Таким чином, ви не робите зайвих запитів на db, порівнюючи номер версії токена jwt з номером користувача.
DaftMonk

5
Не варто сказати, мабуть, тому що існує маса ситуацій, коли ви можете використовувати лексеми з валідаціями, які взагалі не торкаються бази даних. Але я думаю, що в цьому випадку важко уникнути.
DaftMonk

11
Що робити, якщо користувач увійде з декількох пристроїв? Чи повинен один маркер використовуватися для всіх них або повинен увійти в систему недійсним всі попередні?
meeDamian

10
Я погоджуюся з @SergioCorrea. Це зробило б JWT майже такою ж ефективною, як і будь-який інший механізм аутентифікації маркера.
Ed J

40

Я ще цього не пробував, і він використовує багато інформації на основі деяких інших відповідей. Складність тут полягає в тому, щоб уникнути виклику магазину даних на стороні сервера за запитом щодо інформації користувача. Більшість інших рішень потребують пошуку db за запитом у магазині сеансів користувача. Це добре в певних сценаріях, але це було створено для спроби уникнути подібних дзвінків і зробити так, щоб необхідне стан сторони сервера було дуже малим. Ви в кінцевому підсумку відтворить сеанс на стороні сервера, хоч і невеликий, щоб забезпечити всі функції вимкнення сили. Але якщо ви хочете це зробити, це суть:

Цілі:

  • Пом'якшити використання сховища даних (без стану).
  • Можливість примусового виходу всіх користувачів.
  • Можливість примусового виходу з будь-якої особи у будь-який час.
  • Можливість вимагати повторного введення пароля через певний час.
  • Можливість роботи з декількома клієнтами.
  • Можливість примусового повторного входу, коли користувач натискає вихід із певного клієнта. (Щоб запобігти тому, щоб хтось "видаляв" клієнтський маркер після того, як користувач пішов, див. Коментарі для додаткової інформації)

Рішення:

  • Використовуйте короткотривалі (<5м) маркери доступу, сполучені з довгим (кілька годин) клієнтом, що зберігається оновленим маркером .
  • Кожен запит перевіряє чинність автентичності, або оновлення терміну дії маркера.
  • Коли термін доступу закінчується, клієнт використовує маркер оновлення, щоб оновити маркер доступу.
  • Під час перевірки маркера оновлення сервер перевіряє невеликий чорний список ідентифікаторів користувачів - якщо знайдено, відхиляє запит на оновлення.
  • Якщо клієнт не має дійсного (не закінчився терміну) оновлення або токена авторизації, користувач повинен повернутися в систему, оскільки всі інші запити будуть відхилені.
  • За запитом входу, перевірте сховище даних користувачів щодо заборони.
  • Під час виходу - додайте цього користувача до чорного списку сеансу, щоб він повинен був увійти в систему. Вам потрібно буде зберігати додаткову інформацію, щоб не виходити з усіх пристроїв у середовищі з декількома пристроями, але це можна зробити, додавши поле пристрою до поля чорний список користувачів.
  • Щоб примусити повторне введення через x кількість часу - підтримуйте дату останнього входу в токен аутентифікації та перевіряйте її за кожним запитом.
  • Щоб змусити виходити з системи всіх користувачів - скиньте хеш-ключ маркера.

Для цього потрібно підтримувати на сервері чорний список (стан), припускаючи, що таблиця користувачів містить заборонену інформацію про користувача. Недійсний чорний список сеансів - це список ідентифікаторів користувачів. Цей чорний список перевіряється лише під час запиту на оновлення маркера. Записи обов'язкові для проживання на ньому, поки маркер оновлення TTL. Як тільки маркер оновлення закінчиться, користувачеві потрібно буде знову увійти.

Мінуси:

  • Ще потрібно зробити пошук сховища даних за запитом маркера оновлення.
  • Недійсні маркери можуть продовжувати працювати для TTL маркера доступу.

Плюси:

  • Забезпечує бажану функціональність.
  • Дія оновлення токена прихована від користувача при звичайній роботі.
  • Потрібно лише здійснити пошук сховища даних для оновлення запитів замість кожного запиту. тобто 1 кожні 15 хв замість 1 за секунду
  • Мінімізує стан сторони сервера до дуже невеликого чорного списку.

З цим рішенням сховище даних пам’яті на зразок reddis не потрібне, принаймні, не для інформації користувачів, як ви, оскільки сервер здійснює лише db-дзвінок кожні 15 хвилин. Якщо ви користуєтеся Reddis, зберігання дійсного / недійсного списку сесій у ньому було б дуже швидким та простішим рішенням. Не потрібно маркер оновлення. Кожен авторський маркер мав би ідентифікатор сеансу та ідентифікатор пристрою; вони можуть бути збережені у таблиці червоного редактора щодо створення та недійсні, коли це доречно. Тоді їх перевіряли б на кожен запит і відхиляли, коли недійсні.


Як щодо сценарію, коли одна людина встає з комп’ютера, щоб дозволити іншій користуватися тим же комп'ютером? 1-а особа вийде з системи та очікує, що вихід вимкне блокування 2-ї особи. Якщо 2-а особа є середнім користувачем, клієнт може легко заблокувати користувача, видаливши маркер. Але якщо другий користувач має навички злому, користувач встигає відновити ще дійсний маркер для автентифікації 1-го користувача. Здається, немає ніякого способу уникнути необхідності негайно визнати недійсними токени, не зволікаючи.
Джо Лапп

5
Або ви можете видалити свій JWT з сесії / локального сховища чи файлів cookie.
Kamil Kiełczewski

1
Дякую @Ashtonian Провівши великі дослідження, я відмовився від JWT. Якщо ви не звертаєтесь до надзвичайних зусиль, щоб захистити секретний ключ, або якщо ви не делегуєте безпечну реалізацію OAuth, JWT є набагато більш вразливими, ніж регулярні сеанси. Дивіться мій повний звіт: від.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Джо Лапп

2
Використання маркера оновлення - це ключ до дозволу до чорного списку. Чудове пояснення: auth0.com/blog/…
Ромер

1
Це здається мені найкращою відповіддю, оскільки він поєднує короткочасний маркер доступу з довготривалим токеном оновлення, який можна додати до чорного списку. Під час виходу клієнт повинен видалити маркер доступу, щоб другий користувач не міг отримати доступ (навіть якщо маркер доступу залишатиметься дійсним ще кілька хвилин після виходу з системи). @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
Потрібен пошук db!
Роб Еванс

5

Ви можете мати поле "last_key_used" у БД у документі / записі користувача.

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

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

Потім, коли користувач виконує реєстрацію, наприклад, або якщо ви хочете визначити марку недійсною, просто змініть це поле "last_key_used" на інше випадкове значення і будь-які наступні перевірки не зможуть, отже, змушуючи користувача увійти з користувачем і пройти знову.


Це рішення, яке я розглядав, але у нього є такі недоліки: (1) ви або робите пошук DB за кожним запитом, щоб перевірити випадковість (анулюючи причину використання жетонів замість сеансів), або ви лише періодична перевірка після закінчення терміну оновлення (запобігання користувачам негайно вийти з системи або негайно припинити сеанси); та (2) вихід із системи виходу користувача з усіх браузерів і всіх пристроїв (що не є умовно очікуваною поведінкою).
Джо Лапп

Вам не потрібно міняти ключ, коли користувач виходить із системи, лише коли він змінить свій пароль або - якщо ви його надаєте - коли він вирішить вийти з усіх пристроїв
NickVarcha

3

Зберігайте такий список пам'яті

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

Якщо ваші жетони закінчуються через тиждень, тоді чистіть або ігноруйте записи, старіші за них. Зберігайте лише найсвіжіші записи кожного користувача. Розмір списку залежатиме від того, як довго ви зберігаєте маркери та як часто користувачі відкликають свої жетони. Використовуйте db лише тоді, коли таблиця буде змінена. Завантажте таблицю в пам'ять при запуску програми.


2
Більшість виробничих майданчиків працює на більш ніж одному сервері, тому це рішення не працюватиме. Додавання Redis або подібного міжзаступного кешу значно ускладнює систему і часто приносить більше проблем, ніж рішень.
користувач2555515

@ 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)

Примітка . Ви можете надати термін дії Inins під час входу токена так само, як це передбачено в JWT.

Може, це комусь допоможе


2

Чому б не просто використати претензію jti (не має значення) і зберегти її у списку як поле запису користувача (залежне від db, але, принаймні, список, розділений комами)? Немає необхідності в окремому пошуку, оскільки інші, напевно, вказували на те, що ви хочете отримати запис користувача в будь-якому випадку, і таким чином ви можете мати кілька дійсних маркерів для різних екземплярів клієнта ("вихід звідусіль" може скинути список до порожнього)


Так, це. Можливо, встановіть співвідношення «багато хто» між таблицею користувача та новою (сесійною) таблицею, щоб ви могли зберігати метадані разом із jti претензіями.
Пітер Лада

2
  1. Надайте термін придатності для жетонів на 1 день
  2. Ведення щоденного чорного списку.
  3. Помістіть недійсні / маркери виходу в чорний список

Для перевірки токена спочатку перевірте термін дії маркера, а потім чорний список, якщо маркер не закінчився.

Для довгих потреб сеансу повинен бути механізм продовження терміну дії маркера.


4
помістіть жетони в чорний список, і там іде ваше безгромадянство
Керем Байдоган

2

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

Очистіть зберігання / сеанс клієнта

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

Це набагато простіше, ніж регулярне ведення додаткової таблиці з чорного списку та регулярне чищення. Для підтримки декількох пристроїв потрібна додаткова таблиця для входу в систему, дати виходу з деякими додатковими деталями, такими як ОС або клієнт.


2

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

слугувати секретною частиною JWT дозволяють як індивідуальну, так і глобальну недійсну марку. Максимальна гнучкість за пошук db-пошуку / читання під час аутентифікації запиту. Також легко кешувати, оскільки вони рідко змінюються.

Ось приклад:

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
Буде достатньо ще
однієї

2
@giantas, я думаю, що Марк має на увазі частину підпису. Тож замість цього для підписання JWT використовуйте лише один ключ, комбінуйте його, унікальний для кожного клієнта ключ. Тому, якщо ви хочете визначити недійсним весь сеанс користувача, просто змініть його для цього ключа, а якщо ви визнаєте недійсним весь сеанс у вашій системі, просто змініть цей глобальний єдиний ключ.
Tommy Aria Pradana

1

Я зробив це наступним чином:

  1. Створіть unique hash, а потім збережіть його в Redis та JWT . Це можна назвати сеансом
    • Ми також збережемо кількість запитів , які зробив конкретний JWT. Щоразу, коли jwt надсилається на сервер, ми збільшуємо цілі запити . (це необов’язково)

Отже, коли користувач входить у систему, створюється унікальний хеш, зберігається в Redis та вводиться у ваш JWT .

Коли користувач спробує відвідати захищену кінцеву точку, ви захопите унікальний хеш сеансу зі свого JWT , запитаєте redis та побачите, чи відповідає це!

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

Кожен X запит певного JWT зробив, ми створюємо новий унікальний сеанс, зберігаємо його в нашому JWT , а потім чорний список попереднього.

Це означає, що JWT постійно змінюється і перестає лаятися , вкрасти чи щось інше.


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. Недійсний маркер - це вітер - просто видаліть його з таблиці сеансів і створіть нову часову позначку для групи користувача.

Цей самий список може зберігатися в пам’яті (додаток для c #), і це усуне необхідність потрапляння на db для кожного запиту. Список можна завантажити з db при запуску програми
dvdmn

1

Якщо параметр "вихід із усіх пристроїв" прийнятний (у більшості випадків це):

  • Додайте поле версії токена до запису користувача.
  • Додайте значення в цьому полі до претензій, що зберігаються в JWT.
  • Збільшуйте версію щоразу, коли користувач виходить із системи.
  • Під час перевірки маркера порівняйте претензію на її версію з версією, що зберігається в записі користувача, і відхиліть, якщо вона не є однаковою.

Поїздка db для отримання запису користувача у більшості випадків все одно потрібна, тому це не додасть великих витрат на процес перевірки. На відміну від підтримки чорного списку, де завантаження БД є значним через необхідність використання з'єднання або окремого виклику, очистіть старі записи тощо.


0

Я збираюся відповісти. Якщо нам потрібно забезпечити вихід із усіх пристроїв, коли ми використовуємо JWT. Цей підхід використовує пошук бази даних для кожного запиту. Тому що нам потрібен стійкий стан безпеки, навіть якщо відбувається збій на сервері. У таблиці користувачів у нас буде два стовпці

  1. LastValidTime (за замовчуванням: час створення)
  2. Увійшли (за замовчуванням: вірно)

Кожного разу, коли є запит на вихід із користувача, ми оновлюємо LastValidTime до поточного часу та Logged-In - false. Якщо є запит на вхід, ми не будемо змінювати LastValidTime, але для входу буде встановлено значення true.

Коли ми створюємо JWT, у нас буде час створення JWT у корисному навантаженні. Коли ми авторизуємось на послугу, ми перевіримо 3 умови

  1. Чи діє JWT
  2. Чи більший час створення корисного навантаження JWT, ніж User LastValidTime
  3. Чи користувач увійшов у систему

Давайте подивимось практичний сценарій.

Користувач X має два пристрої A, B. Він увійшов на наш сервер о 7 вечора за допомогою пристрою A та пристрою B. (скажімо, термін дії JWT закінчується 12 годин). У обох A і B є JWT з createdTime: 7pm

О 21 годині вечора він втратив свій пристрій B. Він негайно вийшов з пристрою А. Це означає, що тепер у нашій базі даних X користувача LastValidTime є "ThatDate: 9: 00: xx: xxx" та ввійшов як "помилковий".

О 9:30 Mr.Tistent намагається увійти за допомогою пристрою B. Ми перевіримо базу даних, навіть якщо Logged-In є помилковим, тому ми не можемо дозволити.

О 22:00 увійти Mr.X зі свого пристрою А. Тепер пристрій A має JWT із створеним часом: 22:00. Тепер увійшов до бази даних, встановлено значення "вірно"

О 10:30 вечора містерТекер намагається увійти. Навіть незважаючи на те, що Увійшли в систему, це правда. LastValidTime - це 21:00 в базі даних, але JWT B створив час як 7:00. Тож йому не дозволять отримати доступ до служби. Таким чином, використовуючи пристрій B без пароля, він не може використовувати вже створений JWT після виходу одного пристрою.


0

Рішення IAM на зразок Keycloak (над яким я працював) дають кінцеву точку відкликання Token

Кінцева точка відкликання токена /realms/{realm-name}/protocol/openid-connect/revoke

Якщо ви просто хочете вийти з користувачем (або користувачем), ви можете також зателефонувати в кінцеву точку (це просто визнає недійсним маркери). Знову ж таки, у випадку з Кейлоаком, що належить Стороні, потрібно просто назвати кінцеву точку

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

Посилання на випадок, якщо ви хочете дізнатися більше


-1

Це здається справді важким для вирішення без пошуку БД при кожній верифікації лексеми. Альтернативою, яку я можу придумати, є збереження чорного списку недійсних токенів на стороні сервера; який слід оновлювати в базі даних кожного разу, коли відбудеться зміна, щоб зберегти зміни під час перезавантаження, змусивши сервер перевірити базу даних при перезапуску, щоб завантажити поточний чорний список.

Але якщо ви будете зберігати його в пам'яті сервера (глобальна змінна різновид), це не буде масштабуватися на декількох серверах, якщо ви використовуєте більше одного, тож у такому випадку ви можете зберігати його в спільному кеші Redis, який повинен бути налаштування для збереження даних десь (файлова система бази даних?) у випадку, якщо їх потрібно перезапустити, і кожного разу при запуску нового сервера він повинен підписатися на кеш Redis.

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

Це звучить жахливо складно? це робить для мене!

Відмова: Я не використовував Redis.


-1

Якщо ви використовуєте 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);
    });
}

-3

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

Я думаю, що це не найкраще рішення, але це працює для мене.


2
Звичайно, це не найкраще! Кожен, хто має доступ до db, може легко представити себе будь-яким користувачем.
користувач2555515

1
@ user2555515 Це рішення прекрасно працює, якщо маркер, який зберігається в базі даних, зашифрований, як і будь-який пароль, який зберігається в базі даних. Існує різниця між Stateless JWTі Stateful JWT(що дуже схоже на сеанси). Stateful JWTможе отримати вигоду від підтримки білого списку токенів.
TheDarkIn1978
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.