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

При этом из-за характера и механики REST API их защита не всегда проста. Что происходит после того, как пользователь отправит свои учетные данные? Как вы узнаете, что они правильно вошли в систему для своих последующих запросов? Вы не можете сохранить состояние на стороне сервера, чтобы сигнализировать об этом. Ну так что ты делаешь?

В этой статье я хочу поделиться с вами одним очень мощным, но простым способом добиться этого: с помощью веб-токенов JSON.

Что такое JWT

Веб-токены JSON - это открытый и стандартный (RFC 7519) способ безопасного представления личности пользователя во время двустороннего взаимодействия. То есть, когда две системы обмениваются данными, вы можете использовать веб-токен JSON для идентификации своего пользователя без необходимости отправлять личные учетные данные при каждом запросе.

Если вы примените это к нашему контексту REST API, вы увидите, какую пользу может принести эта механика при взаимодействии клиент-сервер.

Вкратце, JWT работает так:

  1. Пользовательское / клиентское приложение отправляет запрос на вход. Другими словами, здесь будет использоваться ваше имя пользователя / пароль (или любой другой тип учетных данных для входа, которые вам необходимо предоставить).
  2. После проверки API создаст веб-токен JSON (подробнее об этом чуть позже) и подпишет его с помощью секретного ключа.
  3. Затем API вернет этот токен обратно в клиентское приложение.
  4. Наконец, клиентское приложение получит токен, проверит его на своей стороне, чтобы убедиться, что он подлинный, а затем продолжит использовать его при каждом последующем запросе для аутентификации пользователя без необходимости больше отправлять его учетные данные.

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

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

Сам токен, возвращаемый API, является (проще говоря) закодированной строкой. Он состоит из трех разных разделов, разделенных друг от друга точкой:

header.payload.signature

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

  • Заголовок будет содержать данные, относящиеся к типу токена, с которым мы имеем дело, и алгоритму, используемому для его создания. Здесь необходимо указать несколько совместимых алгоритмов, но наиболее распространенными являются HS256 и RS256. Это будет зависеть от того, какие стандарты и меры безопасности вы ищете. В этих двух примерах один использует секретный ключ, известный как серверу, так и клиенту, а другой использует закрытый ключ, используемый сервером, в сочетании с открытым ключом, известным клиенту.
  • Полезная нагрузка будет содержать данные, относящиеся к запросу и выполняющему его пользователю. Существует набор стандартных пар ключ / значение, определенных как часть JWT, которые вы можете использовать в своей реализации, например:
  • Проблема (эмитент) - другими словами, способ идентифицировать пользователя, отправившего запрос.
  • Sub (subject) - или, скорее, тема запроса, в нашем случае, вероятно, имеет смысл включить используемый URI
  • Aud (аудитория) - он пытался предоставить некоторую форму идентификации получателя этого токена.
  • Срок действия (срок действия) - токены обычно не действуют вечно, это необходимо для того, чтобы убедиться, что тот, кто их использует, действительно предоставляет недавно сгенерированный токен.

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

  • Подпись - это просто закодированная строка, используемая как сервером, так и клиентом для проверки подлинности полезной нагрузки.

Позвольте мне теперь попытаться связать все, что мы рассмотрели до сих пор, в один пример.

Использование токена

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

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

Это очень простой пример, но его должно быть достаточно, чтобы дать четкое представление о том, почему мы делаем то, что делаем с JWT.

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

POST / api / users-sessions

Полезная нагрузка:

{

«Имя пользователя»: «Фернандо»

«Пароль»: «fernando123»

}

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

В частности, давайте подумаем об информации внутри нашей полезной нагрузки. Вот несколько интересных вариантов:

  • Проблема - содержит имя вошедшего в систему пользователя. Это особенно полезно, поскольку мы можем показать, что в нашем пользовательском интерфейсе
  • Срок действия - потому что мы разрешаем использовать этот новый токен только в течение следующих 8 часов (обычно это время, в течение которого пользователи должны использовать его на ежедневной основе).
  • Admin - логическое значение, описывающее роль пользователя. Это удобно для пользовательского интерфейса, поскольку нам нужно понимать, показывать или скрывать некоторые элементы пользовательского интерфейса.

И для простоты мы будем использовать алгоритм HS256 для кодирования данных, то есть мы будем использовать один и тот же секрет как в нашем клиенте, так и в нашем API. В этом примере нашим секретом будет:

Пример секретного API

Теперь давайте посмотрим, как должны выглядеть разные разделы нашего токена:

Заголовок:

{

«Alg»: «HS256»,

«Тип»: «JWT»

}

Полезная нагрузка:

{

«Проблема»: «Фернандо»

«Опыт»: 1550946689,

«Админ»: ложь

}

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

Итак, у нас есть:

Base64 (заголовок) = ewoiYWxnIjogIkhTMjU2IiwKInR5cCI6ICJKV1QiCn0K

Base64 (полезная нагрузка) = ewoiSXNzIjogImZlcm5hbmRvIiwKIkV4cCI6IDE1NTA5NDY2ODksCiJBZG1pbiI6IGZhbHNlCn0K

HS256 (Base64 (заголовок) + «.» + Base64 (полезная нагрузка), «Пример секретного API») = TseARzVBAtDbU8f3TEiRgsUoKYaW2SbhCWB0QlKpdp8

Последний токен, возвращаемый API, как вы уже догадались:

ewoiYWxnIjogIkhTMjU2IiwKInR5cCI6ICJKV1QiCn0K.ewoiSXNzIjogImZlcm5hbmRvIiwKIkV4cCI6IDE1NTA5NDY2ODksCiJBZgs2pbHBITIG2ODksCiJBZgs2pbHBA1TIGA0CiJBZG2PBHBAI0CiJBZG2PBHBAI6

И здесь появляются интересные моменты - я собираюсь показать вам, почему это так мощно.

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

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

Это предотвратит, например, вмешательство кого-либо в полезную нагрузку сообщения и изменение атрибута «admin» на «true», позволяющее поддельному (или даже действующему пользователю без прав администратора) выполнить привилегированное действие (например, выпустить платеж на какой-то конкретный сотрудник).

Такое действие изменит содержимое полезной нагрузки примерно так:

ewoiSXNzIjogImZlcm5hbmRvIiwKIkV4cCI6IDE1NTA5NDY2ODksCiJBZG1pbiI6IHRydWUKfQo =

Причина, по которой последний токен, отправленный клиентским приложением, будет следующим:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewoiSXNzIjogImZlcm5hbmRvIiwKIkV4cCI6IDE1NTA5NDY2ODksCiJBZG1pbiI6IHRgsWoTDBZZG1pbiI6IHRgspdWoTDeqUhRydWoTDBZGUHRydWoTDVDBUHRydWoTDVDB0CydByUhRydWoTDVD

И подпись для этого токена должна быть:

doRnK7CoVjFOiFmvrQ2wvxcGeQuCYjzUchayNAYx1jw

Который не соответствовал бы отправленному как часть сообщения, тем самым доказывая, что запрос был подделан.

Последние мысли

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

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

Однако в качестве последнего предупреждения я должен упомянуть, что, хотя механика, которую я здесь рассмотрел, довольно проста и доступна для всех, вы должны понимать, что только добавления безопасности JWT в ваш API будет недостаточно. Вы не будете пуленепробиваемым, если просто сделаете это, многие умные хакеры найдут способы обойти это. Есть много других эксплойтов, которые могут (и будут) по-прежнему причинять вам вред, безопасность - это охват всех ваших фронтов, а не просто реализация одной общей схемы безопасности. Дополнительный уровень защиты, который всегда идет рука об руку с JWT, - это защита всего вашего сетевого трафика с помощью HTTPS-соединения. Другими словами, убедитесь, что все, что пользователь отправляет и получает, проходит через порт 443 (или любой другой номер, который вы можете использовать), а не через старый добрый незащищенный порт 80.

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

До следующего!

Разъем: LogRocket, видеорегистратор для веб-приложений.

LogRocket - это интерфейсный инструмент для ведения журнала, который позволяет воспроизводить проблемы так, как если бы они произошли в вашем собственном браузере. Вместо того, чтобы угадывать, почему происходят ошибки, или запрашивать у пользователей снимки экрана и дампы журналов, LogRocket позволяет воспроизвести сеанс, чтобы быстро понять, что пошло не так. Он отлично работает с любым приложением, независимо от фреймворка, и имеет плагины для регистрации дополнительного контекста из Redux, Vuex и @ ngrx / store.

Помимо регистрации действий и состояния Redux, LogRocket записывает журналы консоли, ошибки JavaScript, трассировки стека, сетевые запросы / ответы с заголовками и телами, метаданные браузера и пользовательские журналы. Он также использует DOM для записи HTML и CSS на странице, воссоздавая видео с идеальным пикселем даже для самых сложных одностраничных приложений.

Попробуйте бесплатно.