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

403

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

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

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

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

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

  • 0
    На самом деле, вероятно, нет проблем с безопасностью сразу со многими действительными токенами ... На самом деле существует бесконечное количество действительных токенов ... Итак, зачем тогда иметь токен обновления? Я буду восстанавливать их после каждого запроса, это на самом деле не должно быть проблемой.
  • 0
    Для SPA, проверьте мой пост в блоге: blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
Теги:
security
authentication
jwt

10 ответов

488
Лучший ответ

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

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

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

Хороший шаблон - обновить токен до истечения срока его действия.

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

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

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

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

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

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

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

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

  • 27
    Для подхода, рекомендуемого веб-приложениями, если токен действителен в течение недели, разве мы не заинтересованы в том, чтобы кто-то перехватил токен и затем мог использовать его в течение такого длительного времени? Отказ от ответственности: я не совсем понимаю, о чем говорю.
  • 22
    @wbeange да, перехват - проблема, даже с печеньем. Вы должны использовать https.
Показать ещё 24 комментария
58

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

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

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

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

  • 5
    Я не думаю, что это будет безопасно. Если бы я был злоумышленником, украл ваш токен и отправил его на сервер, сервер проверил бы и увидел, что флаг установлен в значение true, и это здорово, поскольку он заблокировал бы обновление. Я думаю, что проблема заключается в том, что если жертва сменит свой пароль, флаг будет установлен в значение false, и теперь злоумышленник может использовать этот оригинальный токен для обновления.
  • 5
    @ user2924127 нет идеального решения для аутентификации, и всегда будут компромиссы. Если злоумышленник может «украсть ваш токен», у вас могут возникнуть более серьезные проблемы. Установка максимального времени жизни токена была бы полезной настройкой.
Показать ещё 11 комментариев
11

Альтернативным решением для аннулирования JWT без дополнительной защищенной памяти на бэкэнд является реализация нового столбца jwt_version integer в таблице users. Если пользователь хочет выйти из системы или выпустить существующие токены, они просто увеличивают поле jwt_version.

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

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

  • 1
    Это имеет проблемы с несколькими устройствами. По сути, если вы выходите из системы на одном устройстве, оно выходит из системы везде. Правильно?
  • 3
    Эй, это не может быть "проблемой" в зависимости от ваших требований, но вы правы; это не поддерживает управление сеансом для каждого устройства.
11

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

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

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

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

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

  • 1
    Что делать, если компьютер был приостановлен / спать. Таймер будет отсчитывать до истечения срока действия, но токен уже истек. Таймер не работает в этих ситуациях
8

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

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

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

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

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

6

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

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

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

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

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

6

jwt-autorefresh

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

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

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

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

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) => {})

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

2

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

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

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню