Статьи

Как JSON Web Token (JWT) защищает ваш API

Вы, наверное, слышали, что JSON Web Token (JWT) — это современная современная технология защиты API.

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

Посмотрим, смогу ли я объяснить, как JWT может защитить ваш API, не глядя вам в глаза!

Аутентификация API

Определенные ресурсы API требуют ограниченного доступа . Например, мы не хотим, чтобы один пользователь мог изменить пароль другого пользователя.

Вот почему мы защищаем определенные ресурсы, чтобы пользователи предоставляли свои ID и пароль, прежде чем разрешить доступ — другими словами, мы аутентифицируем их.

Сложность защиты HTTP API заключается в том, что запросы не сохраняют состояния  — у API нет возможности узнать, были ли какие-либо два запроса от одного пользователя или нет.

Так почему же мы не требуем, чтобы пользователи указывали свой идентификатор и пароль при каждом вызове API? Только потому, что это будет ужасно для пользователя.

JSON Web Token

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

Для этого было разработано несколько систем, и текущим современным стандартом является JSON Web Token.

На эту тему есть отличная статья , которая дает хорошую аналогию о том, как работают веб-токены JSON.

Представьте, что вместо API вы регистрируетесь в отеле. «Токен» — это пластиковая карта безопасности отеля, которую вы получаете, которая позволяет вам получить доступ к вашей комнате, к услугам отеля, но не к чужой комнате.

Когда вы выезжаете из отеля, вы возвращаете карту. Это аналогично выходу из системы.

Структура токена

Обычно веб-токен JSON отправляется через заголовок HTTP-запросов. Вот как это выглядит:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U

Фактически, токен является частью после «Авторизация: Носитель», которая является просто информацией заголовка HTTP.

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

Во-первых, токен состоит из трех разных строк, разделенных точкой. Эти три строки кодируются с помощью base 64 и соответствуют заголовку , полезной нагрузке и подписи .

// Header
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// Payload
eyJzdWIiOiIxMjM0NTY3ODkwIn0
// Signature
dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U

Примечание: base 64 — это способ преобразования строк, чтобы они не были испорчены при транспортировке через сеть. Это не своего рода шифрование, и любой может легко расшифровать его, чтобы увидеть исходные данные.

Мы можем декодировать эти строки, чтобы лучше понять структуру JWT.

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

{
  "alg": "HS256",
  "typ": "JWT"
}

полезная нагрузка

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

{
  "userId": "1234567890"
}

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

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

Таким образом, это подводит нас к подписи, которая является ключевым элементом для аутентификации токена.

Алгоритмы хеширования

Прежде чем объяснить, как работает подпись, нам нужно определить, что такое алгоритм хеширования.

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

4ae7c3b6ac0beff671efa8cf57386151c06e58ca53a78d83f36107316cec125f

Самое важное свойство хэша состоит в том, что вы не можете использовать алгоритм хеширования для идентификации исходной строки, глядя на хеш .

Существует много различных типов алгоритмов хеширования, но SHA256 обычно используется с JWT.

Другими словами, мы не можем взять вышеуказанный хеш и напрямую выяснить, что исходная строка была «Hello, world». Хеш достаточно сложен, чтобы угадать исходную строку было бы невозможно.

JWT Подпись

Итак, возвращаясь к структуре JWT, давайте теперь посмотрим на третий фрагмент токена, подпись. Это на самом деле нужно рассчитать:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  "secret string"
);

Вот объяснение того, что здесь происходит:

Во-первых, HMACSHA256это имя хеш-функции, которое принимает два аргумента: строку для хеширования и «секрет» (определено ниже).

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

В-третьих, секрет — это произвольная часть данных, которую знает только сервер .

В. Зачем включать заголовок и полезную нагрузку в хэш подписи?

Это гарантирует, что подпись уникальна для данного конкретного токена.

Q. В чем секрет?

Чтобы ответить на этот вопрос, давайте подумаем о том, как бы вы подделали токен.

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

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

Процесс добавления личных данных в хеш называется солением и делает взлом токена практически невозможным.

Процесс аутентификации

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

Авторизоваться

Токен генерируется при входе пользователя в систему и сохраняется в базе данных вместе с моделью пользователя.

loginController.js :

if (passwordCorrect) {
  user.token = generateToken(user.id);
  user.save();
}

Затем токен присоединяется в качестве authorizationзаголовка в ответе на запрос входа в систему.

loginController.js :

if (passwordCorrect) {
  user.token = generateToken(user.id);
  user.save();
  res.headers("authorization", `Bearer ${token}`).send();
}

Аутентификация запросов

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

Когда сервер получает запрос с прикрепленным токеном авторизации, происходит следующее:

  1. Он декодирует токен и извлекает идентификатор из полезной нагрузки.
  2. Он ищет пользователя в базе данных с этим идентификатором.
  3. Он сравнивает токен запроса с тем, который хранится с моделью пользователя. Если они совпадают, пользователь проходит проверку подлинности.

authMiddleware.js :

const token = req.header.token;
const payload = decodeToken(token);
const user = User.findById(payload.id);
if (user.token = token) {
  // Authorized
} else {
  // Unauthorized
}

Выйти

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

logoutController.js :

user.token = null;
user.save();

Завершение

Итак, это очень простое объяснение того, как вы можете защитить API с помощью веб-токенов JSON. Надеюсь, твоя голова не сильно болит.

Тем не менее, в этой теме есть еще много чего, так что вот некоторые дополнительные чтения:


Если вам понравился этот пост, ознакомьтесь с более