JWT (JSON Web Token) - автоматичне продовження терміну придатності


509

Я хотів би впровадити автентифікацію на основі JWT до нашого нового REST API. Але оскільки термін дії встановлений в маркері, чи можливо його автоматично продовжити? Я не хочу, щоб користувачі не потребували входу через кожні X хвилин, якщо вони активно використовували додаток у цей період. Це було б величезним провалом UX.

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

Я виявив, як Auth0 це вирішив. Вони використовують не тільки JWT маркер, але і маркер оновлення: https://docs.auth0.com/refresh-token

Але знову ж таки, щоб реалізувати це (без Auth0), мені потрібно буде зберігати оновлені маркери та підтримувати їх закінчення. Яка реальна користь тоді? Чому б не мати лише один маркер (не JWT) і не тримати термін дії на сервері?

Чи є інші варіанти? Чи використання JWT не підходить для цього сценарію?


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

1
Для SPA - замовлення мого повідомлення в блозі: blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
wong2

2
@maryo Я думаю, що (потенційно) сотні чи тисячі невикористаних дійсних JWT там у будь-який момент часу збільшує ваш напад на атаку і є ризиком для безпеки. На мій погляд, JWT повинні бути видані обережно, оскільки вони мають токени доступу з ключами від замку таким чином.
java-addict301

Відповіді:


589

Я працюю в Auth0, і я брав участь у розробці функції маркера оновлення.

Все залежить від типу програми і ось наш рекомендований підхід.

Веб-додатки

Хороший зразок - оновити маркер до його закінчення.

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

Щоб оновити маркер, вашому API потрібна нова кінцева точка, яка отримує дійсне, не закінчився термін дії JWT та повертає той же підписаний JWT з новим полем закінчення терміну дії. Тоді веб-додаток десь збереже маркер.

Мобільні / Native програми

Більшість власних програм виконують вхід один раз і лише один раз.

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

Проблема з маркером, який ніколи не закінчується, - це ніколи означає ніколи. Що ви робите, якщо втратите телефон? Отже, його потрібно якось ідентифікувати користувачем, і додаток повинен передбачити спосіб відкликати доступ. Ми вирішили використовувати ім’я пристрою, наприклад, "iPad maryo". Тоді користувач може перейти до програми та скасувати доступ до "iPad maryo".

Інший підхід - відкликати маркер оновлення для конкретних подій. Цікава подія - зміна пароля.

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


42
Якщо для веб-додатків рекомендований підхід, якщо маркер дійсний протягом тижня, ми не переймаємось тим, хто перехоплює маркер і може потім користуватися ним тривалий час? Відмова: Я не зовсім знаю, про що я говорю.
користувач12121234

30
@wbeange так перехоплення - це проблема, навіть із файлами cookie. Ви повинні використовувати https.
Хосе Ф. Романьєлло

15
@ JoséF.Romaniello У прикладі веб-додатків для мене все має сенс, крім того, щоб зберігати маркер. Я подумав, що краса JWT полягає в автентичності без громадянства - це означає, що веб-додаток НЕ повинен зберігати маркер під час підписання. Я б подумав, що сервер може просто перевірити дійсність маркера, переконатися, що він знаходиться в терміні придатності, а потім видати оновлений маркер JWT. Не могли б ви детальніше розглянути це? Можливо, я просто ще не розумію JWT.
Ло-Тан

7
Два питання / проблеми: 1- Випадок веб-додатків: чому не можна дозволити маркер, який втратив чинність, отримати оновлення? Скажімо, ми встановлюємо короткий термін дії (1 годину) і здійснюємо поновлення дзвінків на сервер, коли термін дії закінчується, як ви сказали. 2- Чи є питання безпеки щодо збереження хешованих (з випадковою сіллю) паролів у маркері? Ідея полягає в тому, що якщо він є, сервер резервного сервера може перевірити збережений пароль у БД, коли його попросять оновити, та відмовити у запиті, якщо паролі не збігаються. Це охоплює зміну пароля програми для мобільних пристроїв та програм, що дозволить розширити рішення до випадку використання мобільних пристроїв.
псаман

7
-1 Розкриття загальнодоступного API, який сліпо повторно підписує будь-який маркер для продовження терміну його перевірки, є поганим. Тепер у всіх ваших жетонів дієвий нескінченний термін дії. Акт підписання токена повинен містити відповідні перевірки автентичності для кожної заявки, поданої у цьому токені на момент підписання.
Філ

69

У випадку, коли ви самостійно керуєтесь автором (тобто не використовуєте постачальника, як Auth0), може працювати наступне:

  1. Випустіть жетон JWT з відносно коротким терміном дії, скажімо, 15 хвилин.
  2. Програма перевіряє дату закінчення терміну дії маркера до будь-якої транзакції, що вимагає маркер (маркер містить дату закінчення терміну дії). Якщо термін дії закінчився, він спочатку просить API «оновити» маркер (це робиться прозоро до UX).
  3. API отримує запит на оновлення лексеми, але спочатку перевіряє базу даних користувачів, щоб перевірити, чи встановлений прапор "reauth" для цього профілю користувача (маркер може містити ідентифікатор користувача). Якщо прапор присутній, то оновлення лексеми відхилено, інакше буде видано новий маркер.
  4. Повторіть.

Прапор 'reauth' у сервісі бази даних буде встановлений, коли, наприклад, користувач скидає свій пароль. Прапор видаляється, коли користувач увійде в наступний раз.

Крім того, скажімо, що ви маєте політику, згідно з якою користувач повинен увійти в систему щонайменше раз на 72 години. У такому випадку ваша логіка оновлення токена API також перевірятиме дату останнього входу користувача з бази даних користувачів та відхиляє / дозволяє оновити маркер на цій основі.


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

6
@ user2924127 жодне рішення щодо автентичності не є ідеальним, і завжди будуть компроміси. Якщо зловмисник може «викрасти ваш жетон», то у вас можуть виникнути більші проблеми. Встановлення максимального терміну служби маркера було б корисним підключенням до вищезазначеного.
Ян Ян

27
замість того, щоб у базі даних було інше поле, прапор reauth, ви можете включити хеш (bcrypt_password_hash) у маркер. Потім, коли ви оновлюєте маркер, ви просто підтверджуєте, чи хеш (bcrypt_password_hash) дорівнює значенню з маркера. Щоб відмовити в оновленні маркера, потрібно просто оновити хеш пароля.
bas

4
@bas, думаючи про оптимізацію та продуктивність, я думаю, що перевірка хешу пароля була б зайвою і матиме більше наслідків для сервера. Збільшити розмір маркера, тому фірма для підпису / перевірка потребує більше часу. додаткові хеш-обчислення для сервера для пароля. з додатковим полевим підходом ви просто підтверджуєте в перерахунку простим булевим. Оновлення Db рідше для додаткового поля, але частіше оновлює маркер. І ви отримуєте необов'язкове обслуговування примусового індивідуального повторного входу для будь-якого існуючого сеансу (мобільний, веб-сайт тощо).
le0diaz

6
Я думаю, що перший коментар користувача2924127 насправді неправильний. Коли пароль змінено, обліковий запис позначається як такий, що вимагає повторної автентифікації, тому будь-які існуючі маркери, що втратили чинність, втратили чинність.
Ральф

15

Я замислювався, коли переміщував наші програми в HTML5 за допомогою RESTful apis у бекенді. Я придумав таке рішення:

  1. Клієнт видає маркер із тривалістю сеансу 30 хвилин (або будь-який звичайний час сеансу на сервері) після успішного входу.
  2. Створений таймер на стороні клієнта, щоб викликати службу для поновлення маркера до його закінчення. Новий маркер замінить існуючий у майбутніх дзвінках.

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

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

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


1
Що робити, якщо комп'ютер призупинено / спати. Таймер все ще буде рахуватися до закінчення терміну дії, але маркер фактично вже минув. Таймер не працює в таких ситуаціях
Алекс Париж

@AlexParij Ви б порівняти з фіксованим часом, що - щось на зразок цього: stackoverflow.com/a/35182296/1038456
Aparajita

2
Дозволити клієнту запитувати новий маркер із бажаною датою закінчення терміну дії для мене панує як ризик безпеки.
java-addict301

14

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

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

Під час перевірки JWT jwt_versionполе порівнюється поряд із user_idта авторизація надається лише у тому випадку, якщо воно відповідає.


1
У цьому проблеми з кількома пристроями. Якщо ви виходите з одного пристрою, він реєструється скрізь. Правильно?
Сем Уошберн,

4
Гей, це може не бути "проблемою" залежно від ваших вимог, але ви праві; це не підтримує керування сеансом на кожному пристрої.
Оллі Беннетт

Чи це не означає, що jwt_version має зберігатися на стороні сервера таким чином, що схема аутентифікації стає "схожою на сеанс" і перемагає основне призначення JWT?
ChetPrickles

8

Добре запитання - і в самому питанні є велика кількість інформації.

Стаття « Оновити маркери: коли їх використовувати та як вони взаємодіють із JWTs» дає хорошу ідею для цього сценарію. Деякі моменти:

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

Погляньте також на auth0 / angular- jwt angularjs

Для веб-API. читайте Увімкнути OAuth Refresh Tokens у додатку AngularJS за допомогою ASP .NET Web API 2 та Owin


Можливо, я прочитав це неправильно ... Але стаття з заголовком, що починається як "Оновити токмени ...", не містить нічого про лексеми оновлення, окрім того, що ви тут згадали.
Євген Мартинов

8

Я реально реалізував це в PHP за допомогою клієнта Guzzle для створення бібліотеки клієнтів для api, але концепція повинна працювати для інших платформ.

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

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

Цей підхід також дозволяє нам відкликати доступ не пізніше ніж за 5 хвилин, що прийнятно для нашого використання без необхідності зберігання чорного списку жетонів.

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


8

Нижче наведено кроки для відкликання вашого маркера доступу JWT:

1) Під час входу в систему надішліть 2 жетони (маркер доступу, оновити маркер) у відповідь на клієнта.
2) Маркер доступу матиме менший час закінчення, а Refresh матиме тривалий термін дії.
3) Клієнт (передній кінець) зберігатиме маркер оновлення у своєму локальному сховищі та маркер доступу у файлах cookie.
4) Клієнт використовуватиме маркер доступу для виклику apis. Але коли він закінчується, виберіть маркер оновлення з локального сховища та зателефонуйте до програми auth server api, щоб отримати новий маркер.
5) Ваш авторизований сервер відкриє api, який прийме маркер оновлення та перевірить його дійсність та поверне новий маркер доступу.
6) Щойно маркер оновлення закінчиться, Користувач вийде з системи.

Будь ласка, дайте мені знати, якщо вам потрібно більше деталей, я можу поділитися кодом (Java + Spring boot).


Чи можете ви поділитися своїм посиланням на проект, якщо він є у GitHub?
Арун Кумар N


6

jwt-autorefresh

Якщо ви використовуєте вузол (React / Redux / Universal JS), ви можете встановити npm i -S jwt-autorefresh.

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

Повний приклад реалізації

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

відмова від відповідальності: Я є обслуговуючим персоналом


Питання про це, я побачив функцію декодування, яку він використовує. Чи припускає, що JWT можна розшифрувати без використання секрету? Це працює з JWT, які були підписані секретом?
Джан Франко Забарино

3
Так, декодування є декодуванням лише для клієнта і не повинно знати про секрет. Секрет використовується для підписання маркера JWT на стороні сервера для перевірки того, що ваш підпис використовувався для створення JWT спочатку і ніколи не повинен використовуватися у клієнта. Магія JWT полягає в тому, що його корисний вантаж може бути розшифрований на стороні клієнта, а претензії всередині можуть бути використані для створення вашого інтерфейсу без секрету. Єдине, для чого jwt-autorefreshрозшифровується, це вилучити expпретензію, щоб вона могла визначити, наскільки далеко запланувати наступне оновлення.
Чемберлен

1
Ну добре знати, щось не мало сенсу, але зараз це є. Дякую за відповідь.
Джан Франко Забарино,

4

Я вирішив цю проблему, додавши змінну в дані маркера:

softexp - I set this to 5 mins (300 seconds)

Я встановив expiresInваріант на бажаний час, перш ніж користувач буде змушений знову увійти. Міну встановлюють на 30 хвилин. Це повинно бути більше, ніж значення softexp.

Коли мій додаток на стороні клієнта надсилає запит на API сервера (де потрібен маркер, наприклад, сторінка списку клієнтів), сервер перевіряє, чи наданий маркер все ще дійсний чи не заснований на його початковому expiresInзначенні закінчення ( ). Якщо це невірно, сервер відповість статусом, специфічним для цієї помилки, наприклад. INVALID_TOKEN.

Якщо маркер все ще дійсний на основі expiredInзначення, але він вже перевищив softexpзначення, сервер відповість окремим статусом на цю помилку, наприклад. EXPIRED_TOKEN:

(Math.floor(Date.now() / 1000) > decoded.softexp)

Якщо клієнт отримав EXPIRED_TOKENвідповідь, він повинен оновити маркер автоматично, надіславши на сервер запит на поновлення. Це прозоро для користувача та автоматично опікується клієнтським додатком.

Метод оновлення на сервері повинен перевірити, чи маркер все ще дійсний:

jwt.verify(token, secret, (err, decoded) => {})

Сервер відмовиться від поновлення токенів, якщо не вдалося описати вищевказаний метод.


Ця стратегія виглядає добре. Але я думаю, що його слід доповнити якоюсь "максимальною кількістю поновлень", тому що (можливо) користувач sessión може жити назавжди.
Хуан Ігнасіо Барісіч

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

1
це правильно. Я вважаю це "обов'язковим".
Хуан Ігнасіо Барісіч

2

Як щодо цього підходу:

  • Для кожного запиту клієнта сервер порівнює термін ExtekTime токена з (currentTime - lastAccessTime)
  • Якщо закінчення терміну <(поточний час - останній час) , він змінює останній lastAccessedTime на currentTime.
  • У разі бездіяльності браузера протягом тривалості часу, що перевищує expirationTime, або у випадку, якщо вікно браузера було закрито, і time_Type> (currentTime - lastAccessedTime) , а потім сервер може закінчити маркер і попросити користувача знову ввійти.

У цьому випадку нам не потрібна додаткова кінцева точка для оновлення маркера. Був би вдячний за будь-яку подачу.


Це хороший вибір у цей день, це виглядає досить просто для реалізації.
b.ben

4
У цьому випадку, де ви зберігаєте lastAccessedTime? Ви повинні робити це за допомогою бекенда та за кожним запитом, щоб це не стало бажаним рішенням.
antgar9

2

Сьогодні багато людей обирають керувати сеансами з JWT, не усвідомлюючи, що вони відмовляються заради сприйнятої простоти. Моя відповідь пояснюється у другій частині запитань:

Яка реальна користь тоді? Чому б не мати лише один маркер (не JWT) і не тримати термін дії на сервері?

Чи є інші варіанти? Чи використання JWT не підходить для цього сценарію?

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

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

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

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.