JWT токены в Go (Golang): Полное руководство с библиотекой golang-jwt
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанных на JSON. JWT широко используется для аутентификации и обмена информацией между сторонами. В Go (Golang) наиболее популярной и активно поддерживаемой библиотекой для работы с JWT является golang-jwt/jwt/v5. В этой статье мы подробно разберем, как устанавливать, настраивать и использовать эту библиотеку для создания безопасных веб-приложений.
Установка библиотеки golang-jwt
Для начала работы добавьте библиотеку в ваш проект с помощью go get:
go get github.com/golang-jwt/jwt/v5
После установки импортируйте пакет в ваш код:
import (
"github.com/golang-jwt/jwt/v5"
)
Основные компоненты JWT
JWT состоит из трех частей, разделенных точками: Header (заголовок), Payload (полезная нагрузка) и Signature (подпись). Библиотека golang-jwt предоставляет удобные структуры для работы с каждой из них:
- Header — содержит метаданные о типе токена и алгоритме подписи (например, HS256, RS256).
- Payload — содержит claims (утверждения), такие как идентификатор пользователя, время истечения, роли и другие данные.
- Signature — создается с использованием секретного ключа или пары ключей (приватный/публичный).
Создание JWT токена доступа (Access Token)
Рассмотрим пример создания токена доступа с алгоритмом HS256 (симметричное шифрование). Сначала определим структуру с кастомными claims:
type CustomClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
Теперь создадим функцию для генерации токена:
func GenerateAccessToken(userID int, username string, secretKey []byte) (string, error) {
claims := CustomClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: "access",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
Здесь мы устанавливаем время жизни токена 15 минут, что является стандартной практикой для access token. Используйте jwt.NewNumericDate() для корректного форматирования времени.
Верификация и парсинг токена
Для проверки подлинности токена необходимо извлечь claims и проверить подпись:
func ValidateToken(tokenString string, secretKey []byte) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// Проверяем, что используется ожидаемый алгоритм подписи
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("неожиданный метод подписи: %v", token.Header["alg"])
}
return secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("невалидный токен")
}
Важно проверять алгоритм подписи в callback-функции, чтобы избежать атак, связанных с подменой алгоритма.
Создание и обновление Refresh Token
Для повышения безопасности часто используют пару токенов: короткоживущий access token и долгоживущий refresh token. Refresh token позволяет получить новый access token без повторного ввода логина/пароля.
func GenerateRefreshToken(userID int, secretKey []byte) (string, error) {
claims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), // 7 дней
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: "refresh",
ID: strconv.Itoa(userID),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
Для обновления access token с помощью refresh token:
func RefreshAccessToken(refreshTokenString string, secretKey []byte) (string, error) {
// Парсим refresh token
refreshClaims := &jwt.RegisteredClaims{}
_, err := jwt.ParseWithClaims(refreshTokenString, refreshClaims, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
return "", fmt.Errorf("невалидный refresh token: %w", err)
}
// Извлекаем userID из Subject (или из собственного