Введение в JWT
JWT (JSON Web Token) является компактным, автономным методом аутентификации в сети. Он состоит из трех частей: заголовка, полезной нагрузки и подписи. В заголовке содержится информация о типе токена и алгоритме шифрования, полезная нагрузка содержит передаваемые данные (обычно включая информацию о разрешениях и идентификации пользователя), а подпись используется для проверки целостности и действительности токена.
JWT в основном используется в двух сценариях:
- Аутентификация: После входа пользователя каждый последующий запрос будет включать JWT, позволяя пользователю получить доступ к разрешенным маршрутам, услугам и ресурсам.
- Обмен информацией: JWT хорошо подходит для безопасной передачи информации между сторонами, так как они могут быть цифрово подписаны, например, с использованием пар ключей открытого/закрытого типа.
Формат данных JWT
JWT является компактным, URL-безопасным способом представления информации, чтобы ее можно было обменивать между сторонами. Фактически JWT состоит из трех частей, разделенных точками (.
), а именно Header.Payload.Signature
. Далее мы подробно рассмотрим формат данных этих трех частей.
1. Заголовок
Заголовок обычно состоит из двух частей: типа токена — обычно JWT
и используемого алгоритма подписи или шифрования, такого как HMAC SHA256
или RSA
. Заголовок представлен в формате JSON, а затем кодируется в строку с использованием Base64Url. Например:
{
"alg": "HS256",
"typ": "JWT"
}
После кодирования вы можете получить строку, подобную этой:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Полезная нагрузка
Полезная нагрузка состоит из ряда утверждений, содержащих информацию об сущности (обычно пользователя) и другие данные. Полезная нагрузка может включать несколько предопределенных утверждений (также известных как Зарегистрированные утверждения) и пользовательские утверждения (Личные утверждения).
Предопределенные утверждения могут включать:
-
iss
(Issuer): Издатель -
exp
(Expiration Time): Время истечения -
sub
(Subject): Тема -
aud
(Audience): Аудитория -
iat
(Issued At): Выдано -
nbf
(Not Before): Начало действия -
jti
(JWT ID): Уникальный идентификатор JWT
Пример полезной нагрузки может выглядеть следующим образом (в формате JSON):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
Эта информация также будет закодирована с использованием Base64Url, и вы можете получить строку, подобную этой:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. Подпись
Раздел подписи используется для подписи двух закодированных строк, упомянутых выше, с целью проверки того, что сообщение не было изменено во время передачи. Сначала вам нужно указать ключ (если используется алгоритм HMAC SHA256), а затем использовать алгоритм, указанный в заголовке, чтобы подписать заголовок и полезную нагрузку.
Например, если у вас есть следующий заголовок и полезная нагрузка:
HeaderEncoded.HeaderPayload
Псевдокод для подписи их с использованием ключа может быть следующим:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)
Полученная подписанная строка может выглядеть так:
dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
Полный JWT
Комбинирование этих трех частей с точкой (.) в качестве разделителя формирует полный JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
Стандарт JWT является очень гибким и может хранить любую необходимую информацию в полезной нагрузке (но в целях уменьшения размера JWT и в целях безопасности, конфиденциальную информацию стоит не хранить), а также гарантирует целостность этой информации через подпись.
Установка библиотеки JWT для Go
В сообществе Go доступен пакет github.com/golang-jwt/jwt
для работы с JWT. Установка этой библиотеки очень проста, просто выполните следующую команду в каталоге вашего проекта:
go get -u github.com/golang-jwt/jwt/v5
После установки вы можете включить ее в свой import
следующим образом:
import "github.com/golang-jwt/jwt/v5"
Создание простого токена
Чтобы создать простой токен JWT на Go с использованием алгоритма HS256, выполните следующие шаги:
Сначала создайте новый объект Token с использованием алгоритма HS256:
token := jwt.New(jwt.SigningMethodHS256)
Затем используйте метод SignedString
для генерации строкового представления этого токена, передав ключ, который вы будете использовать для подписи.
var mySigningKey = []byte("ваш-секрет-256-бит")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("Произошла ошибка: %v", err)
}
fmt.Println(strToken)
Это сгенерирует простой токен без каких-либо утверждений.
Создание токена с параметрами
Одной из основных функций JWT является передача информации. Эта информация кодируется в токене через утверждения. Например, давайте создадим пользовательские утверждения:
// Структура пользовательских утверждений
type MyClaims struct {
jwt.RegisteredClaims
Username string `json:"username"`
Admin bool `json:"admin"`
}
// Создание токена с пользовательскими утверждениями
claims := MyClaims{
RegisteredClaims: jwt.RegisteredClaims{},
Username: "мое_имя_пользователя",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Секретный ключ для подписи
var mySigningKey = []byte("ваш-секрет-256-бит")
// Генерация токена в строковом формате
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("Произошла ошибка: %v", err)
}
fmt.Println(strToken)
В вышеприведенном коде мы определили структуру MyClaims
для содержания зарегистрированных утверждений, а также некоторой пользовательской информации. Затем мы все еще используем SignedString
для генерации строки токена.
Разбор и валидация токена
Разбор и проверка JWT очень важны, и вы можете сделать это следующим образом:
// Мы будем использовать ту же структуру MyClaims
tokenString := "ваша-JWT-строка"
// Нам нужно определить функцию, которую пакет jwt будет использовать для разбора tokenString
keyFunc := func(t *jwt.Token) (interface{}, error) {
// Проверяем, что используется ожидаемый метод подписи
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Неожиданный метод подписи: %v", t.Header["alg"])
}
// Возвращаем секретный ключ для jwt токена, в формате []byte, совпадающий с ключом, использованным для подписи ранее
return mySigningKey, nil
}
// Разбор токена
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
log.Fatalf("Ошибка разбора: %v", err)
}
if !parsedToken.Valid {
log.Fatalf("Недействительный токен")
}
// На этом этапе parsedToken был проверен, и мы можем прочитать утверждения
fmt.Printf("Пользователь: %s, Администратор: %v\n", claims.Username, claims.Admin)
В вышеприведенном коде мы предоставили строку токена, экземпляр MyClaims
и функцию ключа keyFunc
функции jwt.ParseWithClaims
. Эта функция будет проверять подпись и разбирать токен, заполняя переменную claims
, если токен действителен.