Andrey on .NET | ASP.NET Core: Аутентификация API. Часть 1: JWT

ASP.NET Core: Аутентификация API. Часть 1: JWT

ASP.NET Core logoАутентификация это одна из постоянных составляющих частей API веб-приложения. Привычный вариант реализации с помощью cookie здесь не подходит, т.к. большинство клиентов не смогут его поддерживать. Можно передавать имя пользователя и пароль пользователя с каждым запросом. Но есть способ лучше – использовать Json Web Token (JWT). Реализовать его поддержку в ASP.NET Core приложении достаточно просто. Но для начала разберемся что же такое JWT.

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 значение: JWT
  • alg – Определяет используемый способ генерации цифровой подписи (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, а не для исходных данных.

Создание токена

  1. JSON объекты для блоков HEADER и PAYLOAD должны представлять собой UTF-8 строки.
  2. Каждый блок отдельно сначала представляется в виде массива байт, а затем кодируется в строку с использованием Base64.
  3. Результат записывается через точку: HEADER.PAYLOAD
  4. Используя алгоритм, указанный в параметре alg, генерируется цифровая подпись для строки HEADER.PAYLOAD
  5. Цифровая подпись кодируется в строку с использованием Base64 и является блоком SIGNATURE.
  6. SIGNATURE добавляется через точку к предыдущим двум блокам: HEADER.PAYLOAD.SIGNATURE

Чтение данных из токена

  1. Декодируем блок HEADER из Base64-строки в JSON объект.
  2. Получаем имя алгоритма генерации цифровой подписи из параметра alg.
  3. Декодируем блок SIGNATURE из Base64-строки в массив байт (получаем цифровую подпись).
  4. Проверяем корректность подписи для строки: HEADER.PAYLOAD
  5. В случае успешной проверки декодируем блок PAYLOAD из Base64-строки в JSON объект с утверждениями.

JWT с зашифрованными данными

Вторым вариантом JWT является его представление на базе JWE. Здесь обеспечивается большая безопасность, т.к. утверждения не только подписаны, но и зашифрованы. 

Структура

Структура данного варианта JWT содержит 5 блоков, также разделенных символом "точка":

HEADER.ENCRYPTEDKEY.INITVECTOR.CIPHERTEXT.AUTHTAG

Блоки ENCRYPTEDKEY, INITVECTOR и AUTHTAG опциональны (зависит от используемого алгоритма шифрования и самого процесса) и, при отсутствии, могут быть представлены пустой последовательностью.

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

HEADER

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

  • typ – Указывает тип токена. Рекомендованное стандартом RFC 7519 значение: JWT
  • alg – Определяет используемый способ шифрования блока ENCRYPTEDKEY.
  • enc – Определяет алгоритм шифрования данных.

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

ENCRYPTEDKEY

Блок содержит ключ, использованный для шифрования данных (в случае с JWT это JSON объект c утверждениями).

При этом сам ключ также зашифрован при помощи публичного ключа, предоставленного получателем JWT, и с использованием алгоритма, указанного в параметре alg блока HEADER.

INITVECTOR

Вектор инициализации для алгоритма шифрования.

CIPHERTEXT

В данном блоке хранится JSON объект c утверждениями (аналогично блоку PAYLOAD), зашифрованный при помощи алгоритма указанного в параметре enc блока HEADER и ключа шифрования, сохраненного в ENCRYPTEDKEY.

AUTHTAG

Цифровая подпись блока CIPHERTEXT, полученная в процессе шифрования данных и удостоверяющая целостность результата.

Создание токена

  1. Генерируем:
    • Ключ для шифрования JSON объекта c утверждениями (Content Encryption Key или сокращенно CEK).
    • Вектор инициализации для алгоритма шифрования.
  2. Шифруем JSON объект c утверждениями, используя ключа шифрования и вектор инициализации. В результате получаем зашифрованный список утверждений и его цифровую подпись.
  3. JSON объект для блока HEADER должен представлять собой UTF-8 строку.
  4. Каждый следующий блок отдельно сначала представляется в виде массива байт, а затем кодируется в строку с использованием Base64:
    • HEADER: JSON объект со служебной информацией об используемых алгоритмах.
    • ENCRYPTEDKEY: Ключ CER, зашифрованный при помощи алгоритма alg и публичного ключа от получателя токена.
    • INITVECTOR: Вектор инициализации алгоритма шифрования.
    • CIPHERTEXT: Зашифрованный JSON объект c утверждениями.
    • AUTHTAG: Цифровая подпись блока CIPHERTEXT.
  5. Все блоки записываем через разделитель "точка": HEADER.ENCRYPTEDKEY.INITVECTOR.CIPHERTEXT.AUTHTAG

Чтение данных из токена

  1. Декодируем HEADER из Base64 строки в JSON объект и получаем данные об используемых способах шифрования СЕК и утверждений.
  2. Декодируем данные из Base64-строк следующих блоков:
    1. ENCRYPTEDKEY: Зашифрованный ключ СЕК, который затем надо расшифровать при помощи приватного ключа получателя токена.
    2. INITVECTOR: Вектор инициализации алгоритма шифрования.
    3. CIPHERTEXT: Зашифрованный JSON объект c утверждениями.
    4. AUTHTAG: Цифровая подпись зашифрованного списка утверждений.
  3. При помощи СЕК, вектора инициализации и цифровой подписи расшифровываем блок CIPHERTEXT и получаем строку с JSON объектом, содержащим утверждения.

Плюсы и минусы JWT

Плюсы

  • Возможность реализации технологии единого входа (Single sign-on или сокращенно SSO) которая работает следующим образом:
    • Сервер аутентификации проверяет данные пользователей и генерирует для них JWT.
    • Доверяющие ему, веб-приложения аутентифицируют пользователя по полученному JWT.
    • Пользователю достаточно аутентифицироваться один раз, чтобы получить доступ к группе приложения на определенное время..
  • Простота реализации.
  • Нет необходимости передавать данные пользователя (например, имя и пароль) с каждым запросом. Клиент, получив JWT, даже может не хранить имя и пароль пользователя после ввода.

Минусы

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

Ограничения при использовании JWT

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

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

В следующей части будет рассмотрена реализация аутентификации на базе JWT с цифровой подписью.

Комментарии (2) -

Алексей 23.09.2019 5:13:28

Радует, что кто-то потратил своё время, чтобы написать статью, которая может помочь теперь мне!

Спасибо! Хорошая статья!

Добавить комментарий