JWT (JSON Web Token) автоматическое продление срока действия

509

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

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

Я нашел, как Auth0 решил это. Они используют не только токен JWT, но и токен обновления: https://docs.auth0.com/refresh-token

Но опять же, чтобы реализовать это (без Auth0), мне нужно хранить токены обновления и поддерживать их срок действия. Какова реальная выгода тогда? Почему бы не иметь только один токен (не JWT) и сохранить срок действия на сервере?

Есть ли другие варианты? Разве использование JWT не подходит для этого сценария?

Maryo
источник
1
На самом деле, вероятно, нет проблем с безопасностью сразу со многими действительными токенами ... На самом деле существует бесконечное количество действительных токенов ... Итак, зачем тогда иметь токен обновления? Я буду восстанавливать их после каждого запроса, это на самом деле не должно быть проблемой.
Maryo
1
Для SPA, проверьте мой пост в блоге: blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
wong2
2
@maryo Я думаю, что наличие (потенциально) сотен или тысяч неиспользованных действительных JWT в любой момент времени увеличивает вашу атаку и представляет угрозу безопасности. На мой взгляд, JWT следует выпускать осторожно, поскольку они являются токенами доступа с ключами от замка.
java-addict301

Ответы:

590

Я работаю в Auth0 и участвовал в разработке функции обновления токенов.

Все зависит от типа приложения, и вот наш рекомендуемый подход.

Веб-приложения

Хорошим примером является обновление токена до его истечения.

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

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

Мобильные / родные приложения

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

Идея состоит в том, что токен обновления никогда не истекает, и его всегда можно обменять на действительный JWT.

Проблема с токеном, который никогда не истекает, состоит в том, что никогда не значит никогда. Что вы делаете, если вы потеряете свой телефон? Таким образом, пользователь должен каким-то образом идентифицировать его, а приложение должно предоставлять способ отзыва доступа. Мы решили использовать имя устройства, например, «maryo's iPad». Затем пользователь может зайти в приложение и отозвать доступ к «iPad от maryo».

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

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

Хосе Ф. Романиелло
источник
42
Для подхода, рекомендуемого веб-приложениями, если токен действителен в течение недели, разве мы не заинтересованы в том, чтобы кто-то перехватил токен и затем мог использовать его в течение такого длительного времени? Отказ от ответственности: я не совсем понимаю, о чем говорю.
user12121234
30
@wbeange да, перехват - проблема, даже с печеньем. Вы должны использовать https.
Хосе Ф. Романиелло
15
@ JoséF.Romaniello В вашем примере веб-приложения для меня все имеет смысл, кроме необходимости хранить токен. Я думал, что прелесть JWT заключается в аутентификации без сохранения состояния, то есть веб-приложению НЕ нужно хранить токен, поскольку он подписан. Я бы подумал, что сервер может просто проверить действительность токена, убедиться, что он находится в течение срока действия, а затем выпустить обновленный токен JWT. Не могли бы вы уточнить это? Может быть, я просто еще недостаточно понимаю JWT.
Ло-Тан
7
Два вопроса / проблемы: 1- Случай с веб-приложением: почему токен с истекшим сроком не может быть обновлен? Скажем, мы установили короткий срок действия (1 час) и возобновим вызовы на внутренний сервер, когда токен истечет, как вы сказали. 2- Есть ли проблемы с безопасностью при сохранении хешированного (со случайной солью) пароля в токене? Идея состоит в том, что, если он есть, внутренний сервер может проверить сохраненный в БД пароль по запросу на обновление и отклонить запрос, если пароли не совпадают. Это будет охватывать изменение пароля приложения для мобильных и родных приложений, что позволит распространить решение на использование в мобильных устройствах.
Псама
8
-1 Публичное API-интерфейс, который вслепую переподписывает любой токен для продления срока его действия, - это плохо. Теперь все ваши токены имеют эффективный бесконечный срок действия. Акт подписания токена должен включать в себя соответствующие проверки подлинности для каждого утверждения, сделанного в этом токене во время подписания.
Фил
69

В случае, если вы сами обрабатываете аутентификацию (т.е. не используете провайдера, такого как Auth0), может работать следующее:

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

Флаг 'reauth' в базе данных базы данных будет установлен, когда, например, пользователь сбросил свой пароль. Флаг удаляется, когда пользователь входит в следующий раз.

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

IanB
источник
7
Я не думаю, что это будет безопасно. Если бы я был злоумышленником, украл ваш токен и отправил его на сервер, сервер проверил бы и увидел, что флаг установлен в значение true, и это здорово, поскольку он заблокировал бы обновление. Я думаю, что проблема заключается в том, что если жертва сменит свой пароль, флаг будет установлен в значение false, и теперь злоумышленник может использовать этот оригинальный токен для обновления.
user2924127
6
@ user2924127 нет идеального решения для аутентификации, и всегда будут компромиссы. Если злоумышленник может «украсть ваш токен», у вас могут возникнуть более серьезные проблемы. Установка максимального времени жизни токена была бы полезной настройкой вышеупомянутого.
Янв
27
вместо того, чтобы иметь другое поле в базе данных, флаг reauth, вы можете включить хэш (bcrypt_password_hash) в токен. Затем при обновлении токена вы просто подтверждаете, что хеш (bcrypt_password_hash) равен значению из токена. Чтобы запретить обновление токена, нужно просто обновить хэш пароля.
бас
4
@bas, думая об оптимизации и производительности, я думаю, что проверка хэша пароля была бы избыточной и имела бы больше последствий для сервера. Увеличьте размер токена, чтобы подпись фирмы / проверки занимала больше времени. дополнительные хеш-вычисления для сервера для пароля. с дополнительным полевым подходом вы просто проверяете в пересчете с простым логическим значением. Обновления БД менее часты для дополнительного поля, но чаще обновляются токены. И вы получаете дополнительную услугу принудительного индивидуального повторного входа в систему для любого существующего сеанса (мобильный, веб и т. Д.).
le0diaz
6
Я думаю, что первый комментарий пользователя 2924127 на самом деле неверен. При изменении пароля учетная запись помечается как требующая повторной аутентификации, поэтому любые существующие токены с истекшим сроком действия будут недействительными.
Ральф
15

Я возился при переносе наших приложений на HTML5 с RESTful apis в бэкэнде. Решение, которое я придумал, было:

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

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

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

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

coolersport
источник
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?
Четприклс
8

Хороший вопрос - и в самом вопросе много информации.

Статья « Обновить токены: когда их использовать и как они взаимодействуют с JWT» дает хорошую идею для этого сценария. Некоторые моменты:

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

Также взгляните на auth0 / angular-jwt angularjs

Для веб-API. Прочитайте Включить маркеры обновления OAuth в приложении AngularJS с помощью ASP .NET Web API 2 и Owin

LCJ
источник
Возможно, я неправильно ее прочитал ... Но статья с заголовком, который начинается с "Обновить токены ...", не содержит ничего о токенах обновления, кроме того, что вы упомянули здесь.
Евгений Мартынов
8

Я фактически реализовал это в PHP, используя клиент Guzzle для создания клиентской библиотеки для API, но концепция должна работать для других платформ.

Обычно я выдаю два токена: короткий (5 минут) и длинный, срок действия которого истекает через неделю. Клиентская библиотека использует промежуточное программное обеспечение для попытки одного обновления короткого токена, если она получает ответ 401 на какой-либо запрос. Затем он снова попытается выполнить исходный запрос, и, если ему удалось обновить, он получит правильный ответ, прозрачно для пользователя. Если это не удалось, он просто отправит 401 пользователю.

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

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

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

BytePorter
источник
8

Ниже приведены инструкции по отзыву вашего токена доступа JWT:

1) Когда вы входите в систему, отправьте 2 токена (токен доступа, токен обновления) в ответ клиенту.
2) У маркера доступа будет меньше время истечения, а у обновления будет долгое время истечения.
3) Клиент (Front end) будет хранить токен обновления в своем локальном хранилище и токен доступа в куки.
4) Клиент будет использовать токен доступа для вызова API. Но когда он истекает, выберите токен обновления из локального хранилища и вызовите сервер аутентификации api, чтобы получить новый токен.
5) На вашем сервере аутентификации будет открыт API, который примет токен обновления, проверит его действительность и вернет новый токен доступа.
6) Когда срок действия маркера обновления истечет, пользователь выйдет из системы.

Пожалуйста, дайте мне знать, если вам нужна дополнительная информация, я также могу поделиться кодом (загрузка Java + Spring).

Бхупиндер Сингх
источник
Не могли бы вы поделиться ссылкой на проект, если она есть в GitHub?
Арун Кумар N
6

JWT-автообновление

Если вы используете узел (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())

отказ от ответственности: я сопровождающий

cchamberlain
источник
Вопрос об этом, я видел функцию декодирования, которую он использует. Предполагается ли, что JWT может быть декодирован без использования секрета? Работает ли это с JWT, которые были подписаны с секретом?
Джан Франко Забарино
3
Да, декодирование является декодированием только для клиента и не должно знать о секрете. Секрет используется для подписи серверного токена JWT, чтобы убедиться, что ваша подпись использовалась для первоначальной генерации JWT и никогда не должна использоваться с клиента. Волшебство JWT заключается в том, что его полезная нагрузка может быть декодирована на стороне клиента, а утверждения внутри могут использоваться для создания вашего пользовательского интерфейса без секрета. Единственное, что jwt-autorefreshего декодирует, - это извлечение expзаявки, чтобы она могла определить, как далеко планировать следующее обновление.
cchamberlain
1
О, приятно знать, что-то не имеет смысла, но теперь это имеет значение. Спасибо за ответ.
Джан Франко Забарино
4

Я решил эту проблему, добавив переменную в данные токена:

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

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

Когда мое клиентское приложение отправляет запрос к API сервера (где требуется токен, например, страница списка клиентов), сервер проверяет, является ли отправленный токен по-прежнему действительным или нет, основываясь на его первоначальном expiresInзначении expiration ( ). Если это не верно, сервер ответит с определенным статусом для этой ошибки, например. 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

Как насчет этого подхода:

  • Для каждого клиентского запроса сервер сравнивает expirationTime токена с (currentTime - lastAccessTime)
  • Если expirationTime <(currentTime - lastAccessedTime) , он меняет последний lastAccessedTime на currentTime.
  • В случае неактивности в браузере в течение времени, превышающего expirationTime, или в случае, если окно браузера было закрыто и expirationTime> (currentTime - lastAccessedTime) , а затем сервер может истечь токен и попросить пользователя снова войти в систему.

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

sjaiswal
источник
Является ли это хорошим выбором в этот день, это выглядит довольно легко для реализации.
бенбен
4
В этом случае, где вы храните lastAccessedTime? Вы должны сделать это на бэкэнде и по запросу, так что это становится нежелательным решением с сохранением состояния.
antgar9
2

Сегодня многие люди выбирают для выполнения управления сеансами с JWTs , не зная о том, что они отказываются от ради предполагаемой простоты. Мой ответ подробно описывает 2-ю часть вопросов:

Какова реальная выгода тогда? Почему бы не иметь только один токен (не JWT) и сохранить срок действия на сервере?

Есть ли другие варианты? Разве использование JWT не подходит для этого сценария?

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

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

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

Я написал пост, объясняющий эти недостатки более подробно. Чтобы было ясно, вы можете обойти это, добавив больше сложности (скользящие сеансы, обновления токенов и т. Д.)

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

Даниэль Шписжак
источник