Что такое json web token
Токен авторизации на примере JSON WEB Token
Введение
Начнем с того, что важно уметь различать следующие два понятия: аутентификации и авторизации. Именно с помощью этих терминов почти все клиент-серверные приложения основывают разделение прав доступа в своих сервисах.
Еще одно небольшое введение
Формальное определение
Приступим наконец к работе самого токена. Как я сказал ранее в качестве токенов наиболее часто рассматривают JSON Web Tokens (JWT) и хотя реализации бывают разные, но токены JWT превратились в некий стандарт, именно поэтому будем рассматривать именно на его примере.
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON.
Фактически это просто строка символов (закодированная и подписанная определенными алгоритмами) с некоторой структурой, содержащая полезные данные пользователя, например ID, имя, уровень доступа и так далее. И эта строчка передается клиентом приложению при каждом запросе, когда есть необходимость идентифицировать и понять кто прислал этот запрос.
Принцип работы
Рассмотрим принцип работы клиент серверных приложений, работающих с помощью JWT. Первым делом пользователь проходит аутентификацию, конечно же если не делал этого ранее и в этом есть необходимость, а именно, например, вводит свой логин и пароль. Далее приложение выдаст ему 2 токена: access token и refresh token (для чего нужен второй мы обсудим ниже, сейчас речь идет именно об access token). Пользователь тем или иным способом сохраняет его себе, например, в локальном хранилище или в хранилище сессий. Затем, когда пользователь делает запрос к API приложения он добавляет полученный ранее access token. И наконец наше приложение, получив данный запрос с токеном, проверяет что данный токен действительный (об этой проверке, опять же, ниже), вычитывает полезные данные, которые помогут идентифицировать пользователя и проверить, что он имеет право на запрашиваемые ресурсы. Таким нехитрым образом происходит основная логика работы с JSON Web Tokens.
https://habr.com/ru/post/336082/
Структура токена
Пришло время обсудить структуру токена и тем самым лучше разобраться в его работе. Первое что следует отметить, что JWT токен состоит из трех частей, разделенных через точку:
Полезные данные (playload)
funnytorimage.pw
Рассмотрим каждую часть по подробнее.
Заголовок
Это первая часть токена. Она служит прежде всего для хранения информации о токене, которая должна рассказать о том, как нам прочитать дальнейшие данные, передаваемые JWT. Заголовок представлен в виде JSON объекта, закодированного в Base64-URL Например:
Если раскодировать данную строку получим:
Полезные данные
Что в JSON формате представляет собой:
Именно здесь хранится вся полезная информация. Для данной части нет обязательных полей, из наиболее часто встречаемых можно отметить следующие:
Одной из самых важных характеристик любого токена является время его жизни, которое может быть задано полем exp. По нему происходит проверка, актуален ли токен еще (что происходит, когда токен перестает быть актуальным можно узнать ниже). Как я уже упоминал, токен может помочь с проблемой авторизации, именно в полезных данных мы можем добавить свои поля, которые будут отражать возможности взаимодействия пользователя с нашим приложением. Например, мы можем добавить поле is_admin или же is_preferUser, где можем указать имеет ли пользователь права на те или иные действия, и при каждом новом запросе с легкостью проверять, не противоречат ли запрашиваемые действия с разрешенными. Ну а что же делать, если попробовать изменить токен и указать, например, что мы являемся администраторами, хотя таковыми никогда не были. Здесь мы плавно можем перейти к третьей и заключительной части нашего JWT.
Подпись
Время жизни токена и Refresh Token
Заключение
В данной статье я постарался подробно рассмотреть работу клиент-серверных приложений с токеном доступа, а конкретно на примере JSON Web Token (JWT). Еще раз хочется отметить с какой сравнительной легкостью, но в тоже время хорошей надежностью, токен позволяет решать проблемы аутентификации и авторизации, что и сделало его таким популярным. Спасибо за уделенное время.
JWT — как безопасный способ аутентификации и передачи данных
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.
В простом понимании — это строка в специальном формате, которая содержит данные, например, ID и имя зарегистрированного пользователя. Она передается при каждом запросе на сервер, когда необходимо идентифицировать и понять, кто прислал этот запрос.
В этой статье разберу, что такое Access токен, Refresh токен и как с ними работать.
Для дальнейших разборов будет использован токен:
После того, как посетитель прошел авторизацию в нашей системе, указав свой логин и пароль, система выдает ему 2 токена: access token и refresh токен.
После чего посетитель, когда хочет получить с сервера данные, например, свой профиль, вместе с запросом он передает Access токен, как на примере выше. Сервер, получив его проверяет, что он действительный (об этом чуть ниже), вычитывает полезные данные из него (тот же user_id) и, таким образом, может идентифицировать пользователя.
Токен разделен на три основные группы: заголовок, полезные данные и сигнатура, разделенные между собой точкой.
Это можно проверить прям в браузере, выполнив в консоле или js коде:
Вторым блоком идет eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
Это есть полезные данные, так же закодированные в Base64. После раскодирования получим:
Поскольку необходимо ограничивать токен по времени, поле exp обязательно. По нему можно проверить, актуален ли токен или нет.
Она получается примерно следующим образом:
Берем заголовок, например <"alg":"HS256","typ":"JWT">и кодируем его в base64, получаем ту самую часть eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Тоже самое проделываем с данными eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
После этого склеиваем их и получаем eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
Далее эти данные шифруем с помощью нашего алгоритма HMAC-SHA256 и ключа.
Для проверка токена необходимо проделать ту же операцию.
Как только время выйдет, пользователю снова придется проходить авторизацию. Так вот чтобы этого избежать, существует Refresh токен. С помощью него можно продлить Access токен.
У него, обычно, нет какой-то структуры и это может быть некая случайная строка.
Для проекта odo24.ru я использовал следующий подход.
Генерируется Access токен и после случайная строка, например T6cjEbghMZmybUd_fhE
С нашего нового Access токена eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9.E4FNMef6tkjIsf7paNrWZnB88c3WyIfjONzAeEd4wF0 беру последние шесть знаков, получаю Ed4wF0
Склеиваю и получаю рефреш токен T6cjEbghMZmybUd_fhEEd4wF0
Это сделано для привязки Access токена к Refresh. Для получения новых токенов необходимо передать эти два токена. Делается проверка на их связку и только после валидируется Access токен. Если и второй этап прошел успешно, тогда получаем с базы данных по текущему user_id рефреш токен и сверяем с тем, что к нам пришел. Если они совпадают, тогда генерируются новые токены и в базе данных обновляется Refresh токен на новый.
В моем случае я разделил оба токена и храню в разных местах. Access токен нужен только для идентификации пользователя и на клиенте (JS) он не нужен, поэтому он передается в Cookie (http only).
Refresh токен хранится в LocalStorage и используется только когда Access токен перестал быть актуальным.
Представим ситуацию, когда у нас каким-то образом украли Access токен. Да, это уже плохо и где-то у нас брешь в безопасности. Злоумышленник в этом случае сможет им воспользоваться не более чем на 15-30 минут. После чего токен «протухнет» и перестанет быть актуальным. Ведь нужен второй токен для продления.
Если украли Refresh токен, то без Access токена (который недоступен в JS) продлить ничего нельзя и он оказывается просто бесполезным.
Дату протухания внедрил прям в токен с той целью, чтобы не хранить эту информацию где-то в другом месте, например, в базе данных.
Дата содержит год, месяц, день, час и минуты. Хранится в ASCII
Кодирование даты на Golang:
Всю реализацию на Go можно изучить на Github-е
В этой статье попытался рассказать о взаимодействии двух токенов и как ими пользоваться. В сети достаточно много информации о Access токенах, однако мало, как мне показалось, информации о Refresh токенах.
[ПЕРЕВОД] Аутентификация на основе JSON Web Token в Django и AngularJS: часть первая
Наиболее распространенным методом аутентификации является аутентификация с использованием cookie файлов. Более современный метод аутентификации основан на использовании JSON Web Token (дословно: вэб маркер в формате JSON) и он быстро набирает популярность. В этой статье мы сосредоточимся на нем.
Что такое JSON Web Token?
На самом деле маркеры довольно просты. Представьте себе маркер как ключ в виде карточки, с помощью которого вы каждый день заходите в офис. Когда вас взяли на работу в компанию, вы подтвердили свою личность (логин), предоставив набор учетных данных, таких как водительские права или номер социального страхования. В обмен на эти данных вам выдали ключ (маркер), который разрешает вам находиться в здании. Если бы вы решили уйти с работы или вас уволили (вышли из системы), компания может просто отключить ключ. Конечно, этот ключ может остаться у вас в кармане, но он не даст вам ничего хорошего.
JSON Web Token – это простой маркер в специальном формате. Пока формат не стандартизован, но многие уже используют одну из его реализаций (JWT).
Заголовок
По умолчанию, заголовок содержит только тип маркера и алгоритм, используемый для шифрования.
Тип маркера хранится в ключе «typ». Ключ «typ» игнорируется в JWT (для этой статьи это не важно, но вы можете прочитать, чтобы узнать почему). Если ключ «typ» присутствует, его значение должно быть JWT, чтобы указать, что этот объект является JSON Web Token.
Второй ключ «alg» определяет алгоритм, используемый для шифрования маркера. По умолчанию он должен быть установлен в HS256. Есть целый ряд альтернативных алгоритмов, которые используются в различных реализациях. Полный список с разбивкой по библиотекам можно посмотреть на JWT.io.
Заголовок кодируется в base64.
Полезная нагрузка
В полезной нагрузке хранится любая информация, которую нужно проверить. Каждый ключ в полезной нагрузке известен как «заявление». К примеру, рассмотрим социальную сеть, в которой присоединиться к сообществу можно только по приглашению. Когда мы хотим пригласить кого-то в сообщество, мы отправляем ему письмо с приглашением. Здесь важно проверить, что адрес электронной почты принадлежит человеку, который принимает приглашение, поэтому мы включим этот адрес в полезную нагрузку, для этого сохраним его в ключе «email».
Подпись
Когда у нас есть заголовок и полезная нагрузка, можно вычислить подпись.
Берутся закодированные в base64: заголовок и полезная нагрузка, они объединяются в строку через точку. Затем эта строка и секретный ключ поступает на вход алгоритма шифрования, указанного в заголовке (ключ «alg»). Ключом может быть любая строка. Более длинные строки будут наиболее предпочтительнее, поскольку потребуется больше времени на подбор.
Теперь, когда у нас есть заголовок, полезная нагрузка и подпись, мы можем построить JWT. Окончательный JWT выглядит следующим образом:
Примечание: никогда не сохраняйте маркер в вашей БД. Потому что действительный маркер, эквивалентен паролю, хранить маркер – это все равно, что хранить пароль в открытом виде. Поэтому всегда используйте хеширование перед сохранением.
Реализация проверки подлинности с помощью JSON Web Token
Давайте пойдем дальше и создадим наш проект.
Перейдите в каталог проекта и выполните команды:
Вам нужно убедиться, что настройки STATICFILES_DIRS и TEMPLATE_DIRS присутствуют в django_angular_token_auth/settings.py и они должны выглядеть следующим образом:
Django REST Framework
Теперь нужно установить Django REST Framework. Django REST Framework большой проект с открытым исходным кодом, для тех, кто хочет создавать одностраничные приложения и API для них.
Мы не будем детально рассматривать, как работает Django REST Framework, поэтому если вы не знакомы с ним, посмотрите документацию.
Для установки Django REST Framework, выполните следующую команду:
Django REST Framework должен быть добавлен к вашим установленным приложениям (INSTALLED_APPS в django_angular_token_auth/settings.py:)
Так же необходимо добавить следующие параметры в django_angular_token_auth/settings.py:
Мы не будем использовать SessionAuthentication или BasicAuthentication, но они имеются в Django REST Framework и предлагают работоспособный API из коробки.
django-rest-framework-jwt
Последнее, что нужно сделать, это установить django-rest-framework-jwt. Этот пакет обеспечивает поддержку JWT для Django REST Framework и в качестве JSON Web Token использует реализацию PyJWT. Что бы установить django-rest-framework-jwt выполните следующую команду:
Нужно будет добавить следующие параметры в django_angular_token_auth/settings.py:
Этот параметр указывает срок жизни для наших маркеров и в нашем случае это будет 14 дней. Вы можете изменить его, исходя из собственных предпочтений.
Вы так же должны добавить обновленные настройки REST_FRAMEWORK:
Обратите внимание, что мы добавили:
‘rest_framework_jwt.authentication.JSONWebTokenAuthentication’, в ‘DEFAULT_AUTHENTICATION_CLASSES’.
Создание Django приложения
Теперь давайте создадим приложение Django для хранения в нем представлений и сериализаторов:
Добавим ‘authentication’ в наш INSTALLED_APPS (в django_angular_token_auth/settings.py like so):
Как привило после создания приложения нужно выполнить миграции, для создания моделей в БД. Но мы не будем создавать собственные модели, поэтому об этом не нужно беспокоиться.
Сериализаторы
Для проверки работоспособности API нужно отправлять какие-нибудь данные на AJAX запросы. Самый простой способ это сделать — отправить обратно данные пользователя в нашей системе.
Первое, что нужно сделать, это создать пользователя. Выполните следующую команду и следуйте инструкциям на экране:
После создания пользователя, нужно, чтобы Django отправлял его данные в Angular приложение. Очевидно, что мы не можем отправлять Django объекты нашему приложению на JavaScript, поэтому нужен сериализатор для преобразования объектов в JSON и обратно. Его легко можно создать средствами Django REST Framework.
Создайте файл с именем serializers.py и добавьте в него следующий код:
Этот код указывает Django REST Framework, что мы хотим сериализовать модель User и мы хотим включить только следующие поля: id, username и email.
Представления
С сериализаторами разобрались, давайте теперь перейдем к представлениям.
Для наших целей нужно только одно представление, которое будет возвращать список объектов пользователя.
Django REST Framework предлагает целый ряд представлений, которые выполняют различные функции, например представление списка объектов одного типа. Такую функциональность предлагает класс ListAPIView.
Добавьте следующее в authentication/views.py:
Шаблоны
Создадим простой шаблон, который будет представлять наше приложение.
Создайте файл с именем templates/index.html и добавьте следующий код в него:
В дальнейшем мы добавим некоторый angular код в данный файл.
URL-адреса
Теперь нужно настроить URL-адреса для нашего проекта.
Откройте django_angular_token_auth/urls.py и приведите его к следующему виду:
Давайте рассмотрим, что здесь происходит.
Первое что можно заметить, во втором аргументе для первого URL-шаблона передается строка. Эта строка указывает на представление django-rest-framework-jwt, которое генерирует маркер. Мы не будем рассматривать, как оно работает, но вы можете посмотреть код django-rest-framework-jwt в Github репозитории: django-rest-framework-jwt/rest_framework_jwt/views.py.
Так же мы раньше не использовали представление TemplateView. Оно подобно классу ListAPIView в Django REST Framework предлагает простой способ для обработки шаблонов.
Вы наверно заметили, что регулярное выражение, используемое в последнем URL-шаблоне, будет соответствовать любому URL-адресу.
Для правильной обработки адресов важно, чтобы этот шаблон был последним. Потому что, Django сопоставляет адрес с шаблонами, в том порядке, в котором они определены и при первом же совпадении проверка прекращается. Так как используемое регулярное выражение совпадает с любым адресом, тем самым мы даем шанс всем остальным адресам быть обработанными. Все что не соответствует Django должно быть обработано Angular’ом. Так же пространство имен API /api/v1/, гарантирует, что шаблоны Django и Angular не будут конфликтовать.
JWT для чайников: 5 шагов к пониманию JSON веб-токенов
Что такое JWT, с чем его едят, и как он обеспечивает безопасность вашего приложения? Пошаговое руководство для понимания JSON веб-токенов с нуля.
JWT играет важную роль в веб-безопасности и системах аутентификации пользователей, хотя на первый взгляд ничего особенного из себя не представляет – обычная строка. Впрочем, не совсем обычная, как мы сейчас увидим.
У JSON веб-токенов есть четко определенная структура, определенная стандартом RFC 7519:
Возьмем для примера классическую схему работы приложения:
Что у нас тут есть?
Схема работы очень простая (в теории, по крайней мере):
Создание JSON веб-токенов в 5 шагов
Шаг 1. Заголовок
В заголовке JWT содержатся технические данные – название алгоритма, используемого для генерации подписи токена. Выглядит он вот так:
Это самый обычный JSON-объект.
Поле typ содержит тип токена (для JSON веб-токенов соответственно оно всегда равно JWT).
Шаг 2. Полезная нагрузка
Во втором компоненте JSON веб-токенов ( payload ) хранится информация о пользователе, которую сервер аутентификации передает серверу приложения. Стандарт предусматривает несколько необязательных для заполнения служебных полей, например:
Помимо стандартных ключей, можно передавать любые данные:
Это вновь простой JSON-объект, ничего нового.
Помните, что размер данных полезной нагрузки влияет на общий размер JSON веб-токенов. Если их будет слишком много, передача токена может ухудшить производительность вашего приложения.
Шаг 3. Подпись
Подпись токена вычисляется на основе его заголовка и полезной нагрузки по следующей схеме (псевдокод):
Шаг 4. Cборка JWT
У нас уже есть все необходимые блоки для создания токена:
Теперь можно собрать из них полноценный веб-токен по уже знакомой схеме:
Заголовок и полезная нагрузка предварительно кодируются в Base64URL, а подпись уже в правильном формате:
Этот токен пользователь получит от сервера аутентификации и будет использовать при запросах к серверу приложения.
Узнайте больше о JSON веб-токенах и создайте собственный JWT на jwt.io.
Шаг 5. Верификация JWT
В демо-примере мы использовали хеширование с секретным ключом по алгоритму HS256. Изначально этот ключ известен только серверу аутентификации. Сервер приложения получает его при настройке процесса проверки подлинности.
Когда пользователь отправляет запрос с прикрепленным к нему JWT, приложение может самостоятельно произвести хеширование данных и сравнить результат с полученной подписью. Если строки совпадают, значит вызов поступил из подтвержденного источника и может быть выполнен безопасно.
Пара слов о безопасности данных
Теперь, когда вы разобрались в устройстве и работе JSON веб-токенов, возникает естественный вопрос – защищены ли передаваемые данные?
Мы видели, что токен кодируется и подписывается, но – внимание! – не шифруется! (в чем разница между кодированием и шифрованием?) То есть эта обработка не обеспечивает безопасность данных. Нет никакой гарантии сохранности, поэтому не следует передавать в токенах что-то конфиденциальное. У JWT другое предназначение: проверка подлинности источника данных. Это дополнительный уровень безопасности самого приложения.
Мы разобрали основы JSON веб-токенов, но в этой теме еще много нюансов.
Например, вместо симметричного алгоритма хеширования с одним секретным ключом, можно использовать асимметричный, например, RS256. При этом у сервера аутентификации будет закрытый ключ, а у сервера приложения – открытый. Узнать больше о разнице между симметричными и ассиметричными алгоритмами.
Важный момент: JWT должен отправляться только по защищенному HTTPS-соединению, чтобы предотвратить его перехват.
Хорошей практикой является установка небольшого срока действия JSON веб-токенов. При этом скомпрометированный JWT очень быстро станет недействительным.
Что такое JWT токен?
Эта статья посвящена детальному разбору JWT и его возможностей. Мы изучим структуру токена и построим его с нуля. Затем рассмотрим наиболее распространенные способы использования.
Во время разработки сервиса с пользователям типичной задачей является реализация авторизации.
Когда у вас один сервис, все довольно просто:
Эта строка обычно хранится в куках браузера, а потому называется авторизационной кукой.
Все было отлично, пока не пришли микросервисы. Допустим у нас есть сервер авторизации, который находится на домене auth.foo.com, и парочка других сервисов. Один из ваших сервисов находится на домене blog.foo.com.
Когда пользователь авторизуется на сервере авторизации, то кука будет записана для домена auth.foo.com. Сервис blog.foo.com уже не будет иметь доступ к этой куке.
Это можно исправить, если хранить куку в LocalStorage браузера. Тогда возникает другая проблема. У сервиса blog.foo.com нет возможности проверить куку, так как он ее не выдавал. Сервису необходимо сделать запрос на сервер авторизации, чтобы проверить существование куки и кому она принадлежит. Это создает нагрузку на сервер авторизации.
Решением всех этих проблемы является JWT токен (JSON Web Token), который возлагает задачу по обработке аутентификационных данных на сами микросервисы, а следовательно позволяет избежать различных ошибок авторизации, увеличить производительность и улучшить масштабируемость приложения.
Однако неправильное использование JWT может негативно сказаться на безопасности приложения. Разберем примеры использования JWT, разберем распространенные ошибки в реализации схем аутентификации с применением JWT, рассмотрим основные виды атак на эти схемы и дадим рекомендации по их предотвращению.
Формат JWT
JWT состоит из трех основных частей: заголовка (header), нагрузки (payload) и подписи (signature). Заголовок и нагрузка формируются отдельно в формате JSON, кодируются в base64, а затем на их основе вычисляется подпись. Закодированные части соединяются друг с другом, и на их основе вычисляется подпись, которая также становится частью токена.
Бывают и исключения, когда в JWT отсутствует подпись. Подобный случай будет рассмотрен далее.
Header
Заголовок является служебной частью и состоит из двух полей: типа токена, в данном случае JWT, и алгоритма хэширования подписи:
Значение поля typ зачастую игнорируется приложениями, однако стандарт не рекомендует отказываться от него для обеспечения обратной совместимости.
Поле alg обязательно для заполнения. В приведенном случае был применен алгоритм HS256 (HMAC-SHA256), в котором для генерации и проверки подписи используется единый секретный ключ.
Для подписи JWT могут применяться и алгоритмы асимметричного шифрования, например RS256 (RSA-SHA256). Стандарт допускает использование и других алгоритмов, включая HS512, RS512, ES256, ES512, none и др.
Использование алгоритма none указывает на то, что токен не был подписан. В подобном токене отсутствует часть с подписью, и установить его подлинность невозможно.
Закодируем этот JSON в base64 и получим: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
Полезна нагрузка — это любые данные, которые вы хотите передать в токене. Стандарт предусматривает несколько зарезервированных полей:
Все эти claims не являются обязательными, но их использование не по назначению может привести к коллизиям.
Любые другие данные можно передавать по договоренности между сторонами, использующими токен. Например, payload может выглядеть так:
Поскольку набор полей в части полезной нагрузки произвольный, приложение может хранить в этой части практически любые данные. Например, для ускорения работы приложения в полезной нагрузке могут храниться Ф.И.О. пользователя, чтобы не запрашивать эти сведения каждый раз из базы данных.
Payload не шифруется при использовании токена, поэтому не стоит передавать в нем чувствительные данные. Например, паспортные данные.
Размер payload не ограничен, но не стоит передавать в нем много данных. Из-за размера токена могут начаться проблемы с производительностью.
У нас уже есть 2/3 токена, осталось сгенерировать подпись.
Signature
Подпись генерируется следующим образом: Закодированные заголовок и полезная нагрузка объединяются с точкой («.») в качестве разделителя. Затем эта строка хешируется указанным в header алгоритмом. Результат работы алгоритма хеширования и есть подпись.
В нашем примере токен будет выглядеть так: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRoIFNlcnZlciIsInN1YiI6ImF1dGgiLCJleHAiOjE1MDU0Njc3NTY4NjksImlhdCI6MTUwNTQ2NzE1MjA2OSwidXNlciI6MX0.9VPGwNXYfXnNFWH3VsKwhFJ0MazwmNvjSSRZ1vf3ZUU
Другие сервисы знают пароль, которым авторизационный сервис подписывает токены. Или у них есть публичный ключ, которым они могут проверить подпись. Поэтому если кто-то попытается изменить данные в токене, то он станет не валидным. Например, если вы храните в токене роль пользователя, а злоумышленник попробует подставить туда “admin”, то он не сможет сгенерировать новую подпись для поддельного токена.
Официальный сайт jwt.io предлагает два алгоритма хэширования: HS256 и RS256. Но можно использовать любой алгоритм с приватным ключом.
Официальный сайт jwt.io предлагает два алгоритма хэширования: HS256 и RS256. Но можно использовать любой алгоритм с приватным ключом.
Аутентификация
Схема аутентификации с использованием JWT предельно проста.
Пользователь вводит свои учетные данные в приложении или доверенном сервисе аутентификации. При успешной аутентификации сервис предоставляет пользователю токен, содержащий сведения об этом пользователе.
При последующих обращениях токен передается приложению в запросах от пользователя: в cookie, заголовках запроса, POST или GET параметрах и т. д.
Получив токен, приложение сперва проверяет его подпись. Убедившись, что подпись действительна, приложение извлекает из части полезной нагрузки сведения о пользователе и на их основе авторизует его.
Можно реализовать всю эту схему вручную, а можно использовать одну из библиотек указанных на jwt.io.
Преимущества JWT
Перечислим преимущества использования JWT в сравнении с классической схемой аутентификации, использующей сессии.
Во-первых, подход с использованием токенов позволяет не хранить информацию обо всех выданных токенах, как при классической схеме. Когда пользователь обращается к приложению, он передает ему свой токен. Приложению остается только проверить подпись и извлечь необходимые поля из полезной нагрузки.
В-третьих, при использовании отдельного сервиса аутентификации становится возможным организовать единую точку входа в различные сервисы с одними и теми же учетными данными (SSO). Единожды пройдя процедуру аутентификации, пользователь сможет получить доступ со своим токеном к тем ресурсам, которые доверяют этому сервису аутентификации.
В-четвертых, как было указано ранее, приложение может хранить в части полезной нагрузки практически любые данные, что при грамотной архитектуре приложения может существенно увеличить производительность.
Благодаря перечисленным факторам схема аутентификации с использованием JWT широко используется в различных корпоративных приложениях. Особенно популярна эта схема в тех приложениях, которые реализуют парадигмы микросервисной архитектуры: при таком подходе каждый сервис получает необходимые ему сведения о пользователе непосредственно из токена, а не тратит время на получение этой информации из базы данных.
Уязвимости JWT
В этом разделе будут рассмотрены основные атаки на JWT и даны рекомендации по их предотвращению.
Перехват токена
Перехват пользовательского токена может привести к ряду неприятных последствий.
Во-первых, так как JWT передается в открытом виде, для получения хранящихся в части полезной нагрузки исходных данных достаточно применить к этой части функцию base64UrlDecode. То есть злоумышленник, перехвативший токен, сможет извлечь хранящиеся в токене данные о пользователе.
В соответствии с лучшими практиками для предотвращения подобной угрозы рекомендуется:
Во-вторых, злоумышленник, перехвативший токен, сможет его переиспользовать и получить доступ к приложению от лица пользователя, чей JWT был перехвачен.
Здесь рекомендации будут следующие:
Refresh tokens
В современных схемах аутентификации, основанных на JWT, после прохождения аутентификации пользователь получает два токена:
Access token при таком подходе имеет сильно ограниченное время жизни (например, одну минуту). Refresh token же имеет длительное время жизни (день, неделя, месяц), но он одноразовый и служит исключительно для обновления access token пользователя.
Схема аутентификации в таком случае выглядит следующим образом:
Подбор ключа симметричного алгоритма подписи
При использовании симметричных алгоритмов для подписи JWT (HS256, HS512 и др.) злоумышленник может попытаться подобрать ключевую фразу.
Подобрав ее, злоумышленник получит возможность манипулировать JWT-токенами так, как это делает само приложение, а следовательно сможет получить доступ к системе от лица любого зарегистрированного в ней пользователя.
В нашем примере из первой части статьи для подписи JWT в качестве ключевой фразы была использована строка password. Она простая, короткая и содержится во всех основных словарях для перебора паролей. Злоумышленнику не составит труда подобрать эту ключевую фразу с использованием программ John the Ripper или hashcat.
Рекомендации для защиты от атаки в этом случае такие:
Использование алгоритма none
Как было упомянуто в первой части статьи, использование в заголовке JWT алгоритма none указывает на то, что токен не был подписан. В подобном токене отсутствует часть с подписью, и установить его подлинность становится невозможно.
Рассмотрим подобную атаку на нашем примере. Наш токен в незакодированном виде выглядит следующим образом
Предположим, мы хотим, чтобы приложение считало нас администратором. Для этого необходимо установить значение admin в поле role полезной нагрузки. Но при внесении в токен этого изменения подпись токена станет невалидной, и приложение не примет такой JWT.
Для обхода защитного механизма мы можем попытаться изменить значение поля alg в заголовке токена на none. Наш токен примет следующий вид:
Поскольку мы используем алгоритм none, подпись отсутствует. В закодированном виде наш JWT будет выглядеть так: eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0
Этот токен мы и передадим на сервер. Уязвимое приложение, проверив заголовок JWT и обнаружив в нем alg: none, примет этот токен без всяких проверок, как если бы он был легитимным, в результате чего мы получим привилегии администратора.
Чтобы защититься от такой атаки:
Изменение алгоритма подписи
При использовании асимметричных алгоритмов подпись токена осуществляется с использованием приватного ключа сервиса, а проверка подписи — с использованием публичного ключа сервиса.
Некоторые реализации библиотек для работы с JWT содержат логические ошибки, заключающиеся в том, что при получении токена, подписанного с использованием симметричного алгоритма (например, HS256), для проверки подписи в качестве ключевой фразы будет использован публичный ключ сервиса. Поскольку публичный ключ сервиса не засекречен, злоумышленник может легко получить его и использовать для подписи собственных токенов.
Для рассмотрения примера этого варианта атаки нам понадобится новый JWT:
В кодированном виде он будет выглядеть следующим образом: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6InVzZXIifQ.YLOVSKef-paSnnM8P2JLaU2FiS8TbhYqjewLmgRJfCj1Q6rVehAHQ-lABnKoRjlEmHZX-rufHEocDxGUYiGMjMexUQ3zt-WqZITvozJ4pkvbV-mJ1nKj64NmqaR9ZkBWtmF-PHJX50eYjgo9rzLKbVOKYOUa5rDkJPHP3U0aaBXFP39zsGdOTuELv436WXypIZBeRq2yA_mDH13TvzegWCK5sjD4Gh177bCq57tBYjhGIQrDypVe4cWBPlvwFlmG8tdpWGu0uFp0GcbTAfLUlbTSuGROj88BY0XeUs0iqmGlEICES3uqNx7vEmdT5k_AmL436SLedE0VHcyxve5ypQ
Поскольку в этом случае мы используем для подписи алгоритм RS256, нам понадобятся публичный и приватный ключи.
Публичный ключ Приватный ключ
Для тестов мы будем использовать сайт jwt.io
Исходный JWT
Как и в предыдущем примере, модифицируем токен:
В кодированном виде заголовок и зполезная нагрузка будут выглядеть следующим образом:
Остается только подсчитать подпись с использованием публичного ключа сервиса.
Для начала переводим ключ в hex-представление:
Затем генерируем подпись с использованием openSSL:
Полученное значение E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g добавляем к уже имеющейся строке, и наш токен принимает следующий вид: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0.E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g
Подставляем в поле secret на jwt.io наш публичный ключ, и JWT успешно проходит проверку. Не забудьте поставить галочку secret base64 encoded.
Для предотвращения такой атаки рекомендуется:
Заключение
JSON Web Tokens — популярная и удобная технология. При правильном использовании JWT избавляет от распространенных ошибок недостаточной авторизации, позволяет просто и удобно распределить информационные потоки между сервисами, организовать единую точку входа в различные сервисы с одними и теми же учетными данными и даже повысить производительность сервиса.
Вместе с тем при неправильном использовании JWT можно подвергнуть свою систему существенным рискам, вплоть до компрометации учетных записей абсолютно всех пользователей системы.
Итак, для безопасного использования JWT следует:
После беглого знакомства с JSON web tokens может сложиться впечатление, что они встроены в современные механизмы авторизации и аутентификации, такие как OAuth или OpenID. Однако это не совсем так. JSON токены действительно используются в этих системах, но не являются их частью. Более того, сфера их использование гораздо шире авторизации.