ASP.NET Core: Аутентификация API. Часть 1: JWT
Аутентификация это одна из постоянных составляющих частей API веб-приложения. Привычный вариант реализации с помощью cookie здесь не подходит, т.к. большинство клиентов не смогут его поддерживать. Можно передавать имя пользователя и пароль пользователя с каждым запросом. Но есть способ лучше – использовать Json Web Token (JWT). Реализовать его поддержку в ASP.NET Core приложении достаточно просто. Но для начала разберемся что же такое JWT.
- Часть 1: JWT
- Часть 2: Реализация с JWS
- Часть 3: Реализация с JWE
JWT
Json Web Token является способом безопасной передачи данных в формате JSON и описан в стандарте RFC 7519. Токен представляет собой строку, содержащую блоки информации, разделенные точкой.
JWT использует в качестве основы два следующих стандарта представления данных:
- JSON Web Signature (JWS) – JSON данные, подписанные цифровой подписью (RFC 7515).
- JSON Web Encryption (JWE) – зашифрованные JSON данные (RFC 7516).
JWS и JWE ориентированные на произвольные данные. Однако именно их стандарты и форма записи используется для создания JWT в целях аутентификации.
JWT с цифровой подписью
JWT с цифровой подписью на базе JWS наиболее простой вариант представления токена и во многих случаях отлично подходит для целей аутентификации в API веб-приложения.
Рассматриваемый вариант JWT не защищает непосредственно данные, но гарантирует их подлинность при помощи цифровой подписи. Содержимое блоков, закодированное при помощи Base64 в строку, можно легко посмотреть даже в консоли браузера. Для этого достаточно вызвать JavaScript метода
atob(…)отдельно для каждого блока (за исключением цифровой подписи, разумеется).
Структура
JWS состоит из трех текстовых блоков, разделенных символом “точка”:
HEADER.PAYLOAD.SIGNATURE
HEADER
Первым расположен заголовок, содержащий JSON объект со служебной информацией. Как правило, он содержит два важных параметра:
typ– Указывает тип токена. Рекомендованное стандартом RFC 7519 значение: JWTalg– Определяет используемый способ генерации цифровой подписи (SHA256 или RSA).
Пример:
{
"typ": "JWT",
"alg": "SHA256"
}
PAYLOAD
Следующий блок содержит непосредственно данные в виде JSON объекта. При этом каждая пара “свойство (ключ) – значение” называется “утверждением” (claim). Существует набор стандартных утверждений:
iss– (Issuer) Строка или URL идентифицирующие приложение создавшее токен.sub– (Subject) Тема. Может использоваться для создания токенов с различными целями в рамках одного приложения (с одинаковымiss).aud– (Audience) Аудитория токена (например “web” или имя клиентского приложения).nbf– (Not Before) Время активации токена, до которого он будет считаться не валидным.exp– (Expiration Time) Время, после которого токен будет считаться не валидным. Для продолжения работы клиент должен будет запросить новый токен.iat– (Issued At) Дата генерации токена.jti– (JWT ID) Уникальный идентификатор токена.
Все стандартные утверждения JWT опциональны. Их имена сокращены до 3х символов в целях уменьшения размера JSON объекта.
Также в объект можно включать любые произвольные утверждения как, например, name в примере ниже:
{
"iss": "demo app",
"name": "admin"
}
SIGNATURE
В блоке SIGNATURE находится подпись, которая удостоверяет подлинность блоков HEADER.PAYLOAD (включая точку межу ними). Для ее создания используется метод указанный в параметре alg заголовка HEADER.
Обратите внимание что цифровая подпись:
может быть сделана как с использованием симметричного, так и асимметричного алгоритма.
создается и проверяется для строки HEADER.PAYLOAD, обе части которой уже закодированы с использованием Base64, а не для исходных данных.
Создание токена
- JSON объекты для блоков HEADER и PAYLOAD должны представлять собой UTF-8 строки.
- Каждый блок отдельно сначала представляется в виде массива байт, а затем кодируется в строку с использованием Base64.
- Результат записывается через точку: HEADER.PAYLOAD
- Используя алгоритм, указанный в параметре
alg, генерируется цифровая подпись для строки HEADER.PAYLOAD - Цифровая подпись кодируется в строку с использованием Base64 и является блоком SIGNATURE.
- SIGNATURE добавляется через точку к предыдущим двум блокам: HEADER.PAYLOAD.SIGNATURE
Чтение данных из токена
- Декодируем блок HEADER из Base64-строки в JSON объект.
- Получаем имя алгоритма генерации цифровой подписи из параметра
alg. - Декодируем блок SIGNATURE из Base64-строки в массив байт (получаем цифровую подпись).
- Проверяем корректность подписи для строки: HEADER.PAYLOAD
- В случае успешной проверки декодируем блок PAYLOAD из Base64-строки в JSON объект с утверждениями.
JWT с зашифрованными данными
Вторым вариантом JWT является его представление на базе JWE. Здесь обеспечивается большая безопасность, т.к. утверждения не только подписаны, но и зашифрованы.
Структура
Структура данного варианта JWT содержит 5 блоков, также разделенных символом “точка”:
HEADER.ENCRYPTEDKEY.INITVECTOR.CIPHERTEXT.AUTHTAG
Блоки ENCRYPTEDKEY, INITVECTOR и AUTHTAG опциональны (зависит от используемого алгоритма шифрования и самого процесса) и, при отсутствии, могут быть представлены пустой последовательностью.
Полный вариант записи токена подразумевает обмен публичными ключами между генератором JWT и его получателем (получателями). Но это не исключает и другие вариант. Например, если JWT создается и проверяется на одном и том же сервере, то нет смысла передавать ключи в JWT. Кроме того, различные сервера могу обменяться ключами и другим способами.
HEADER
Как и в первом варианте, данный блок хранит служебную информацию в виде JSON объекта. Можно выделить следующие параметры:
typ– Указывает тип токена. Рекомендованное стандартом RFC 7519 значение: JWTalg– Определяет используемый способ шифрования блока ENCRYPTEDKEY.enc– Определяет алгоритм шифрования данных.
Кроме того, могут присутствовать и другие параметры, которые описывают дополнительные свойства использованных алгоритмов шифрования.
ENCRYPTEDKEY
Блок содержит ключ, использованный для шифрования данных (в случае с JWT это JSON объект c утверждениями).
При этом сам ключ также зашифрован при помощи публичного ключа, предоставленного получателем JWT, и с использованием алгоритма, указанного в параметре alg блока HEADER.
INITVECTOR
Вектор инициализации для алгоритма шифрования.
CIPHERTEXT
В данном блоке хранится JSON объект c утверждениями (аналогично блоку PAYLOAD), зашифрованный при помощи алгоритма указанного в параметре enc блока HEADER и ключа шифрования, сохраненного в ENCRYPTEDKEY.
AUTHTAG
Цифровая подпись блока CIPHERTEXT, полученная в процессе шифрования данных и удостоверяющая целостность результата.
Создание токена
- Генерируем:
- Ключ для шифрования JSON объекта c утверждениями (Content Encryption Key или сокращенно CEK).
- Вектор инициализации для алгоритма шифрования.
- Шифруем JSON объект c утверждениями, используя ключа шифрования и вектор инициализации. В результате получаем зашифрованный список утверждений и его цифровую подпись.
- JSON объект для блока HEADER должен представлять собой UTF-8 строку.
- Каждый следующий блок отдельно сначала представляется в виде массива байт, а затем кодируется в строку с использованием Base64:
- HEADER: JSON объект со служебной информацией об используемых алгоритмах.
- ENCRYPTEDKEY: Ключ CER, зашифрованный при помощи алгоритма
algи публичного ключа от получателя токена. - INITVECTOR: Вектор инициализации алгоритма шифрования.
- CIPHERTEXT: Зашифрованный JSON объект c утверждениями.
- AUTHTAG: Цифровая подпись блока CIPHERTEXT.
- Все блоки записываем через разделитель “точка”: HEADER.ENCRYPTEDKEY.INITVECTOR.CIPHERTEXT.AUTHTAG
Чтение данных из токена
- Декодируем HEADER из Base64 строки в JSON объект и получаем данные об используемых способах шифрования СЕК и утверждений.
- Декодируем данные из Base64-строк следующих блоков:
- ENCRYPTEDKEY: Зашифрованный ключ СЕК, который затем надо расшифровать при помощи приватного ключа получателя токена.
- INITVECTOR: Вектор инициализации алгоритма шифрования.
- CIPHERTEXT: Зашифрованный JSON объект c утверждениями.
- AUTHTAG: Цифровая подпись зашифрованного списка утверждений.
- При помощи СЕК, вектора инициализации и цифровой подписи расшифровываем блок CIPHERTEXT и получаем строку с JSON объектом, содержащим утверждения.
Плюсы и минусы JWT
Плюсы
- Возможность реализации технологии единого входа (
Single sign-onили сокращенноSSO) которая работает следующим образом:- Сервер аутентификации проверяет данные пользователей и генерирует для них JWT.
- Доверяющие ему, веб-приложения аутентифицируют пользователя по полученному JWT.
- Пользователю достаточно аутентифицироваться один раз, чтобы получить доступ к группе приложения на определенное время..
- Простота реализации.
- Нет необходимости передавать данные пользователя (например, имя и пароль) с каждым запросом. Клиент, получив JWT, даже может не хранить имя и пароль пользователя после ввода.
Минусы
- Необходимо следить чтобы ключи, используемые для подписи или шифрования, не попали в чужие руки. Во многих случаях заменить ключ при утечке не проблема, но это приведет к необходимости повторной аутентификации всех пользователей, не смотря на время жизни поученных ими JWT.
Ограничения при использовании JWT
В случае с веб-приложениями, JWT, как правило, передается в заголовке каждого запроса. Поэтому стоит взять за практику сохранять только действительно нужные утверждения.
Кроме того, сам заголовок ограничен в размере конкретной реализацией сервера, что определяет и максимальный размер самого JWT. Аналогичные ограничения могут быть в других случаях (лимит на размер сообщения в очереди и т.д.).
В следующей части будет рассмотрена реализация аутентификации на базе JWT с цифровой подписью.