Долгое время аутентификация пользователя в сети заключалась в хранении некоторых очень простых данных (например, идентификатора пользователя) в браузере пользователя в виде файла cookie. Это работало довольно хорошо (и до сих пор работает для многих приложений), но иногда вам требуется немного большей гибкости.
Традиционно, чтобы получить такую гибкость, вам приходилось хранить «состояние» на сервере, которое могло бы сообщать вам такие вещи, как, например, кто является пользователь, какие у него разрешения и т. Д. Для хранения этих данных обычно требовалось иметь выделенные данные. store, например Redis или базы данных, что усложняет ваше приложение.
За последние несколько лет появился новый открытый стандарт, который все чаще принимается некоторыми ведущими веб-сайтами и приложениями. Этим стандартом является веб-токен JSON (JWT). В этой статье мы покажем вам, как они работают, и, что более важно, почему вы действительно хотите их использовать.
Примечание: Стандарт JWT становится немного более сложным с дополнительными Jws и JWE стандартов, поэтому в этой статье мы будем сосредотачиваться только на то , что указано в JWT.
Зачем использовать веб-токены JSON?
При использовании классических файлов cookie сеанса, используемых на большинстве веб-сайтов, вы храните токен на стороне клиента в файле cookie браузера (обычно), который затем используется для поиска данных сеанса на стороне сервера. Это отлично работает для большинства простых веб-сайтов, но вам необходимо учесть все эти дополнительные данные на стороне сервера с каким-либо типом хранилища данных.
С другой стороны, с JWT вы можете вместо этого хранить эти данные сеанса на стороне клиента, не беспокоясь о том, что они будут манипулировать пользователем или другой третьей стороной, благодаря тому, что они подписаны сервером. Ваш классический файл cookie сеанса также подписан / зашифрован, но, по крайней мере, теперь данные находятся у пользователя.
Хранение данных на стороне клиента также снижает сложность на стороне сервера, поскольку вам больше не нужно хранилище данных с низкой задержкой для частого хранения и выборки данных сеанса.
Вы также получаете немного больше гибкости с типом данных, которые вы храните с помощью JWT. Поскольку данные находятся в формате JSON, вы можете использовать глубоко вложенные структуры данных. Конечно, это также можно сделать на стороне сервера данных сеанса, но опять же, с JWT вам не нужно иметь дело с другой базой данных.
Итак, кроме уменьшения сложности и увеличения гибкости, зачем еще вам использовать JWT? Какие данные обычно там хранятся? В стандарте ( RFC 7519 ) не указан максимальный размер токена, поэтому теоретически вы можете хранить столько данных, сколько хотите, при условии, что вы не превышаете максимальный размер вашего носителя. Однако это не рекомендуется, поскольку JWT должны быть максимально компактными.
Для большинства приложений, с которыми я сталкивался, было бы полезно иметь по крайней мере следующую информацию:
- Идентификация пользователя (UID, электронная почта и т. Д.): Удобна для простого поиска дополнительных сведений о пользователе в вашей базе данных.
- Отметка времени истечения срока действия токена: в большинстве случаев токены не должны длиться вечно, и пользователю необходимо повторно пройти аутентификацию.
- JWT ID: подходит для отмены JWT, вынуждая пользователя снова входить в систему.
Конечно, в вашем токене может быть гораздо больше информации, но обычно это хорошая отправная точка для нового приложения.
Также рекомендуется, чтобы JWT хранились в локальном хранилище, а не в файлах cookie, хотя файлы cookie также можно использовать. Это рекомендуемый способ, поскольку совместное использование ресурсов между источниками (CORS) по умолчанию не использует файлы cookie. Вместо этого вы должны отправить JWT в заголовке «Авторизация», используя схему «Носитель».
Теперь давайте посмотрим, из чего состоит JWT, в следующем разделе.
Состав
Веб-токен JSON состоит из трех разделов - заголовка, полезной нагрузки и подписи. И заголовок, и полезная нагрузка хранят данные в формате JSON, который закодирован в Base64 , в то время как подпись создается путем подачи заголовка и полезной нагрузки с помощью алгоритма подписи (который указан в заголовке) вместе с секретом. С помощью этой подписи можно проверить подлинность токена и убедиться, что данные не были подделаны неавторизованной третьей стороной.
Общая структура токена выглядит примерно так:
hhhhhhhh.ppppppppp.sssssssss
Где h
- заголовок, p
- полезная нагрузка, а s
- подпись.
Заголовок
Раздел заголовка предназначен для предоставления важной информации о токене. Обычно он сообщает вам тип («тип») токена (который мы рекомендуем всегда использовать «JWT») и алгоритм, используемый для подписи токена:
{
"typ":"JWT",
"alg":"HS256"
}
В этом примере заголовок утверждает, что «HS256» или HMAC-SHA256 использовался для подписи токена.
Если ваш JWT немного сложнее и имеет вложенную подпись или шифрование, вам также следует использовать параметр заголовка «cty» со значением «JWT», в противном случае его можно не указывать. С другой стороны, JWT без подписи или шифрования должен иметь значение «alg», равное «none».
Полезная нагрузка
Полезная нагрузка токена - это то место, где вы фактически можете передать свою информацию приложению. Чтобы попытаться сделать JWT более совместимыми между различными приложениями, были установлены некоторые стандарты, определяющие, что и как передаются определенные данные. Это делается с помощью «претензий». JWT определяет три типа требований:
- Зарегистрированные заявки: это заявки, зарегистрированные в реестре заявлений на веб-токены IANA JSON . Ничего из этого не требуется устанавливать, но они приведены в качестве отправной точки для повышения совместимости приложений. Некоторые примеры: эмитент («iss»), тема («sub»), аудитория («aud») и срок действия («exp»). Для ограничения длины токена предпочтительнее использовать короткие имена утверждений.
- Публичные претензии: это утверждения, которые могут быть определены «по желанию», что означает отсутствие явных ограничений. Чтобы предотвратить конфликты между именами, вы должны либо зарегистрировать их в реестре утверждений веб-токенов IANA JSON, либо использовать имя, устойчивое к конфликтам.
- Частные претензии: обычно это информация, более конкретная для вашего приложения. Хотя общедоступное утверждение может содержать такую информацию, как «имя» и «адрес электронной почты», частные утверждения будут более конкретными, например, «идентификатор пользователя» или «область авторизации».
Хотя я считаю важным придерживаться стандарта JWT в отношении утверждений об именах, от вас не требуется использовать то, что мы упомянули здесь (или любые другие, указанные в стандарте). Вместо этого вы можете просто использовать свои собственные, но не ожидайте, что ваши токены будут взаимодействовать с другими сторонними приложениями.
Подпись
Подпись создается с использованием кодировки Base 64 (подробнее об этом ниже) заголовка и полезной нагрузки, которые объединены точкой ('.'). Чтобы эта подпись работала, необходимо использовать секретный ключ, известный только исходному приложению.
Псевдокод для создания подписи будет выглядеть примерно так:
unsignedToken = base64Encode(header) + '.' + base64Encode(payload)
signature = HS256('your-secret-key', unsignedToken)
Обычно используется криптографический алгоритм HMAC с SHA-256 или подпись RSA с SHA-256.
В дополнение к этому существует еще один стандарт, который определяет гораздо больше алгоритмов для использования при аутентификации и шифровании на основе токенов. Это стандарт веб-алгоритма JSON (JWA). Если вам нужны дополнительные параметры шифрования и подписи, проверьте это.
Кодирование JWT
В этом разделе мы рассмотрим реальный пример использования наших данных JSON и создания из них веб-токена. Вот данные, с которыми мы будем работать:
Заголовок
{
"alg": "HS256",
"typ": "JWT"
}
Полезная нагрузка
{
"sub": "123456789",
"name": "Scott Robinson",
"awesome": true
}
Как вы помните ранее в этой статье, полученный токен должен иметь форму:
hhhhhhhh.ppppppppp.sssssssss
Первый шаг - это кодирование URL-адресов Base64 для каждого раздела. Кодирование заголовка сверху дает нам следующее:
base64Header = base64Encode(header)
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
То же самое с полезной нагрузкой сверху дает:
base64Payload = base64Encode(payload)
// eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiU2NvdHQgUm9iaW5zb24iLCJhd2Vzb21lIjp0cnVlfQ
Отсюда нам нужно сгенерировать нашу подпись. Как вы, наверное, заметили из данных заголовка, мы будем использовать алгоритм HS256 для его генерации.
signature = HS256(base64Header + '.' + base64Payload, 'super-sekret')
// WwR-0ZlhUBRkBlUBZ6l6lWvBZNGmdAsageRCvry3bY0
Чтобы получить последний токен, мы соединяем эти три части вместе, соединяя каждую точкой ('.'), Например:
token = base64Header + '.' + base64Payload + '.' + signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkiLCJuYW1lIjoiU2NvdHQgUm9iaW5zb24iLCJhd2Vzb21lIjp0cnVlfQ.WwR-0ZlhUBRkBlUBZ6l6lWvBZNGmdAsageRCvry3bY0
Это довольно просто сделать самостоятельно, но, вероятно, вам и не придется. Мне еще предстоит использовать язык программирования, в котором еще нет библиотеки для генерации таких токенов JWT для вас. Ссылки на некоторые из самых популярных библиотек можно найти в разделе «Библиотеки » ниже.
JWT-декодирование и проверка
Теперь, когда мы знаем, как кодировать JWT, декодировать довольно просто. Мы начинаем с разделения токена по точкам, а затем декодируем каждый раздел отдельно:
base64Header, base64Payload, signature = token.split('.')
header = base64Decode(base64Header)
payload = base64Decode(base64Payload)
// Read the header and payload data here
Немного более сложная часть - это когда нам нужно проверить подпись. Мы делаем это, воссоздавая подпись из заголовка и полезной нагрузки, используя наш секрет, а затем проверяем, соответствует ли она подписи, которую мы получили. Если это не так, значит, либо токен не является подлинным, либо он был каким-то образом изменен.
computedSig = HS256(base64Header + '.' + base64Payload, 'super-sekret')
if computedSig != signature:
print('FAILED')
Имейте в виду, что в большинстве случаев вам следует проверить заголовок, чтобы увидеть, какой алгоритм использовался в подписи, но мы можем пропустить эту часть здесь для наших целей.
Библиотеки
К счастью, нет недостатка в поддержке JWT на всех популярных веб-языках. Вот несколько примечательных библиотек, на которые стоит обратить внимание:
- pyjwt (Python)
- jsonwebtoken (Node.js)
- java-jwt (Java)
- ruby-jwt (Рубин)
- jwt-go (Вперед)
Есть еще немало других, которые я здесь не перечислял, но это те, с которыми вы чаще всего сталкиваетесь при разработке веб-приложений.
Заключение
Это небольшое отличие от традиционного способа ведения дел, но, на мой взгляд, оно того стоит. На мой взгляд, гибкость возможности аккуратно и безопасно хранить больше данных на стороне клиента перевешивает любую небольшую путаницу, которая может возникнуть у вас по поводу JWT на первых порах. Просто помните, что JWT необязательно использовать в каждом приложении, но если у вас есть некоторые сложности, они могут вам пригодиться.
Надеюсь, эта статья помогла вам понять, что они не так запутаны, как могут показаться изначально. Я настоятельно рекомендую вам прочитать RFC для веб-токенов JSON . Его на удивление легко читать, и он предоставляет много полезной информации о стандарте. Если вы собираетесь использовать это как авторизацию для учетных записей ваших пользователей, то стоит потратить время, чтобы понять все детали JWT.
Как вы использовали JWT в своих приложениях? Каким был ваш опыт их использования? Дайте нам знать об этом в комментариях!