Идея авторизации по JWT-токенам

Курс по Django: https://stepik.org/a/183363

На этом занятии мы поговорим об авторизации с помощью JSON Web Token, сокращенно JWT. Идея JWT-токенов сильно отличается от обычных токенов, с которыми мы познакомились на предыдущем занятии. Но прежде, чем мы коснемся самого процесса авторизации, вначале нужно знать, что из себя вообще представляют JWT-токены.

Перед вами пример JWT-токена:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZ
XhwIjoxNjM4NjgxOTAzLCJqdGkiOiJlNzM2N2MxZTY4YTc0ZTI2YjA5ZDkwZ
DQyZjc2MDBkZSIsInVzZXJfaWQiOjV9.a-OlrfisMAEB5VKyoarOI_eWQFwN6KOy2Bvg7iQ9uDs

Он состоит из трех частей, разделенных точками:

  • header – заголовка;
  • payload – полезные данные;
  • signature – подпись.

Фрагменты header и payload – это обычные JSON-строки, закодированные алгоритмом base64 в последовательность ASCII-букв и цифр. А signature – это фрагмент шифрования строки «header.payload», например, с помощью алгоритма HS256 с использованием секретного ключа:

Затем, полученные три фрагмента объединяются через точку и получается JWT-токен:

jwt_token = header.payload.signature

Обратите внимание, алгоритм base64 не шифрует данные, а кодирует, то есть, представляет их в несколько ином виде и не более того. Это означает, что данные в JWT-токенах не защищены от просмотра. Но, вот изменить данные в header или в payload не получится, так как подпись signature тогда будет некорректной и сервер «поймет», что данные в JWT-токене не соответствуют действительности. Это единственная защита, которая имеется в этих токенах. То есть, мы можем совершенно спокойно читать данные, но не менять.

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

Первый вопрос здесь, зачем вообще понадобилось изобретать новый тип токенов? Разве не хватало прежних? Не хватало. Например, когда единая учетная запись используется множеством независимых сервисов, как это сделано в компании Google. Нам достаточно один раз авторизоваться (ввести логин/пароль) и мы получаем доступ ко всему его инструментарию. Реализовать это на уровне обычных токенов было бы неудобно, так как каждый сервис должен был бы получать доступ к БД с токенами пользователя, добавлять и удалять их. То есть, сервисы уже не были бы полностью независимыми, им пришлось бы дублировать некоторый функционал, а это источник многих потенциальных ошибок и проблем.

В связи с этим и была придумана несколько иная схема авторизации пользователей. Вначале клиент, естественно, должен пройти процедуру авторизации. Для этого сервис перенаправляет его запрос на сервер авторизации, который функционирует в рамках всего инструментария компании. Если учетные данные пользователя оказались корректными, то сервер возвращает пользователю два JWT-токена, которые мы обозначим, как access_token и refresh_token. Дополнительно refresh_token сохраняется в БД сервера авторизации, а на стороне клиента оба: access_token и refresh_token.

Зачем нужны два токена, вы сейчас поймете. Для доступа к сервисам используется первый access_token, который имеет короткое время действия, например, 5 минут. Так как access_token содержит в себе информацию о пользователе, а также цифровую подпись, то сервис «понимает» какой пользователь запрашивает доступ и, кроме того, имеет возможность проверить корректность токена по его подписи, так как все сервисы «знают» секретный ключ, которым были закодированы данные пользователя. То есть, прямая подмена данных в access_token со стороны злоумышленника очень непростая задача.

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

Ладно, хорошо, но тогда по истечении 5 минут пользователю снова придется вводить логин/пароль для авторизации в сервисах, чтобы получить новый access_token на следующие 5 минут? Согласитесь, перспектива не очень приятная? Поэтому и существует второй refresh_token, который имеет заметно большее время жизни от суток и более. Когда время действия access_token заканчивается на сервер авторизации отправляется refresh_token и пользователю назначаются новые access_token и refresh_token. Обратите внимание, refresh_token тоже меняется при запросе нового access_token. Соответственно в БД и локальном хранилище на стороне клиента эти токены обновляются. Так происходит защита сервисов при краже access_token.

Конечно, злоумышленник и за несколько минут может наделать делов в наших профайлах, но так мы имеем хоть какую-то защиту от взлома. Согласитесь, это лучше, чем если бы третьи лица получали доступ на гораздо больший срок. Ну а абсолютной гарантии от взлома все равно ни одна система не может обеспечить. Поэтому, соблюдайте цифровую гигиену и защищайте свои данные от хакерских атак. Включайте двойную авторизацию по логину/паролю и СМС на критических сервисах, например, банковских, госуслугах, брокерских счетах и т.п. Другого человечество еще не придумало.

Но внимательный зритель может заметить, что злоумышленник также может украсть и refresh_token. Тогда он получает постоянный доступ в сервисы под нашими учетными данными! Да, и это самый неприятный момент в этом алгоритме. Злоумышленник сможет использовать refresh_token, пока честный пользователь явно не разлогиниться (выйдет из системы). В этом случае, все refresh_token этого пользователя помечаются как недействительные и злоумышленник не сможет продлить access_token. Ему придется уже вводить логин/пароль, которые он, скорее всего, не будет знать.

Вот общая схема работы JWT-токенов. Опять же, конкретная реализация алгоритмов авторизации/аутентификации может отличаться. Здесь по разному можно организовывать БД для хранения refresh_token, а иногда и access_token. По разному сохранять их на стороне клиента, защищая дополнительно refresh_token от кражи. И так далее. То есть, конкретные реализации в разных системах могут заметно отличаться.

Ну, и, конечно же, открытый вопрос, нужно ли именно вам в ваших проектах использовать JWT-токены? На мой взгляд, на простых сайтах, которые лишь взаимодействуют с большим числом различных клиентов (браузеров, смартфонов, носимой электроники и т.п.), достаточно применять обычные токены. И на практике именно так и делают. Большинство сайтов при реализации API используют простую авторизацию и аутентификацию по токенам и этого вполне достаточно. JWT-токены имеют несколько специфические приложения. Конечно, для крупных компаний со множеством независимых сервисов, это один из вариантов авторизации. Но и в небольших проектах им тоже есть место. Например, на файлообменниках можно организовать временный доступ к скачиваемому файлу, как раз, через JWT-токен. При этом refresh_token можно совсем отбросить и не использовать. Возможны и другие частные приложения. Хотя, я не стану выступать в роли оракула и наперед предсказывать ограниченность применения этой новой идеи авторизации. Кто знает, возможно, через некоторое время она станет доминирующей при реализации алгоритмов авторизации во многих сервисах? Но пока она имеет свою небольшую нишу, где и применяется.

На следующем занятии мы посмотрим, как можно реализовать авторизацию JWT-токенами на нашем тестовом сайте.

Курс по Django: https://stepik.org/a/183363