golang-jwt для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

golang-jwt для Go: полное руководство с примерами

Библиотека golang-jwt (официальное название — github.com/golang-jwt/jwt/v5) — это де-факто стандартная реализация JSON Web Tokens (JWT) для языка Go. Она предоставляет разработчикам мощный и гибкий инструментарий для создания, подписи, верификации и декодирования JWT-токенов. JWT — это компактный и самодостаточный способ передачи информации между сторонами в виде JSON-объекта, который может быть верифицирован, так как он подписан цифровой подписью. Токены JWT широко используются для аутентификации, авторизации и безопасного обмена данными в REST API, микросервисных архитектурах и веб-приложениях.

Зачем нужна эта библиотека? В современной веб-разработке на Go практически любое приложение, требующее аутентификации пользователей (например, API-серверы, микросервисы, системы единого входа), сталкивается с необходимостью работы с JWT. Библиотека golang-jwt берёт на себя всю сложность: генерацию и проверку подписей (HMAC, RSA, ECDSA, EdDSA), парсинг заголовков и полезной нагрузки, обработку стандартных полей (exp, iat, iss, sub) и кастомных claims. Без неё пришлось бы вручную реализовывать криптографические операции и парсинг JSON, что чревато ошибками и уязвимостями.

Ключевые преимущества golang-jwt: полная поддержка спецификации RFC 7519, простота использования благодаря fluent-интерфейсу, высокая производительность (использует стандартную библиотеку crypto), возможность расширения через кастомные signing methods и валидаторы. Библиотека активно поддерживается сообществом, имеет более 7 тысяч звёзд на GitHub и используется в таких проектах, как Kubernetes, Docker, Grafana и многие другие.

Установка

Установка библиотеки производится стандартным для Go способом через go get. Убедитесь, что у вас установлен Go версии 1.18 или выше (рекомендуется 1.21+).

go get github.com/golang-jwt/jwt/v5

После выполнения команды в вашем go.mod появится зависимость. Импортировать библиотеку в код нужно следующим образом:

import "github.com/golang-jwt/jwt/v5"

Быстрый старт — минимальный рабочий пример

Создадим простой пример: генерация и верификация JWT-токена с использованием симметричного ключа (HMAC-SHA256).

package main

import (
    "fmt"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

func main() {
    // 1. Секретный ключ (должен храниться в безопасности)
    secretKey := []byte("my-super-secret-key-12345")

    // 2. Создание токена
    claims := jwt.MapClaims{
        "user_id": 12345,
        "role":    "admin",
        "exp":     time.Now().Add(time.Hour * 24).Unix(), // срок действия 24 часа
        "iat":     time.Now().Unix(),                     // время создания
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString(secretKey)
    if err != nil {
        panic(err)
    }
    fmt.Println("Сгенерированный токен:", tokenString)

    // 3. Верификация и парсинг токена
    parsedToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Проверяем, что метод подписи — HMAC
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("неожиданный метод подписи: %v", token.Header["alg"])
        }
        return secretKey, nil
    })

    if err != nil {
        panic(err)
    }

    // 4. Извлечение claims
    if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok && parsedToken.Valid {
        fmt.Printf("Пользователь ID: %v\n", claims["user_id"])
        fmt.Printf("Роль: %v\n", claims["role"])
    } else {
        fmt.Println("Токен невалиден")
    }
}

Основные методы и функции

Рассмотрим ключевые методы библиотеки. Для каждого приведена сигнатура, описание и рабочий пример.

1. jwt.NewWithClaims

Сигнатура: func NewWithClaims(method SigningMethod, claims Claims) *Token

Описание: Создаёт новый JWT-токен с указанным методом подписи и набором claims (утверждений). Возвращает указатель на структуру Token, который затем можно подписать.

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "sub": "user123",
    "exp": time.Now().Add(time.Hour).Unix(),
})

2. token.SignedString

Сигнатура: func (t *Token) SignedString(key interface{}) (string, error)

Описание: Подписывает токен с помощью предоставленного ключа и возвращает готовую JWT-строку. Тип ключа зависит от метода подписи (для HMAC — []byte, для RSA — *rsa.PrivateKey).

tokenString, err := token.SignedString([]byte("secret-key"))

3. jwt.Parse

Сигнатура: func Parse(tokenString string, keyFunc Keyfunc) (*Token, error)

Описание: Парсит и верифицирует JWT-строку. Принимает функцию Keyfunc, которая должна вернуть ключ для проверки подписи. Возвращает распарсенный токен или ошибку.

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return []byte("secret-key"), nil
})

4. jwt.ParseWithClaims

Сигнатура: func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)

Описание: Аналог Parse, но позволяет передать конкретную структуру claims для автоматического заполнения. Удобно для работы с кастомными типами.

type CustomClaims struct {
    jwt.RegisteredClaims
    UserID int `json:"user_id"`
}

claims := &CustomClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
    return []byte("secret-key"), nil
})

5. jwt.MapClaims

Сигнатура: type MapClaims map[string]interface{} (реализует интерфейс Claims)

Описание: Простейший тип claims, представляющий собой обычную map. Подходит для быстрых прототипов и случаев, когда структура claims динамическая.

claims := jwt.MapClaims{
    "username": "john_doe",
    "admin":    true,
    "exp":      time.Now().Add(15 * time.Minute).Unix(),
}

6. jwt.RegisteredClaims

Сигнатура: type RegisteredClaims struct { ... }

Описание: Структура со стандартными полями JWT (iss, sub, aud, exp, nbf, iat, jti). Рекомендуется встраивать в кастомные структуры claims для соблюдения спецификации.

type MyClaims struct {
    jwt.RegisteredClaims
    Scope string `json:"scope"`
}

claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{
        Subject:   "user123",
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
        IssuedAt:  jwt.NewNumericDate(time.Now()),
    },
    Scope: "read:users",
}

7. jwt.NewNumericDate

Сигнатура: func NewNumericDate(t time.Time) *NumericDate

Описание: Создаёт объект NumericDate из time.Time. Используется для установки полей ExpiresAt, IssuedAt и NotBefore в RegisteredClaims.

expTime := jwt.NewNumericDate(time.Now().Add(24 * time.Hour))

8. jwt.SigningMethodHMAC

Сигнатура: type SigningMethodHMAC struct { ... }

Описание: Представляет метод подписи HMAC (HS256, HS384, HS512). Используется для проверки алгоритма подписи при верификации токена.

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("неверный метод подписи: %v", token.Header["alg"])
    }
    return []byte("secret"), nil
})

9. jwt.ValidationError

Сигнатура: type ValidationError struct { ... }

Описание: Тип ошибки, возвращаемой при невалидности токена. Содержит флаги для определения причины: истек срок, неверная подпись, неправильный метод и т.д.

if err != nil {
    if ve, ok := err.(*jwt.ValidationError); ok {
        if ve.Errors&jwt.ValidationErrorExpired != 0 {
            fmt.Println("Токен истек")
        }
    }
}

10. jwt.NewParser

Сигнатура: func NewParser(options ...ParserOption) *Parser

Описание: Создаёт кастомный парсер с дополнительными опциями: пропуск проверки expiration, валидация audience, использование JSON-декодера и т.д.

parser := jwt.NewParser(
    jwt.WithValidMethods([]string{"HS256", "HS384"}),
    jwt.WithAudience("my-service"),
)
token, err := parser.Parse(tokenString, keyFunc)

Реальный практический пример

Разработаем полноценный HTTP-сервер с аутентификацией через JWT. Сервер будет иметь два эндпоинта: /login (возвращает токен) и /protected (доступен только с валидным токеном).

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

// Конфигурация
var (
    secretKey = []byte("change-me-in-production-12345")
    users     = map[string]string{
        "admin": "password123",
        "user":  "pass456",
    }
)

// Структура для кастомных claims
type AppClaims struct {
    jwt.RegisteredClaims
    Username string `json:"username"`
    Role     string `json:"role"`
}

// Обработчик /login
func loginHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var creds struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }

    if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }

    // Проверка учетных данных
    expectedPass, ok := users[creds.Username]
    if !ok || expectedPass != creds.Password {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
        return
    }

    // Определяем роль
    role := "user"
    if creds.Username == "admin" {
        role = "admin"
    }

    // Создаем claims
    now := time.Now()
    claims := AppClaims{
        RegisteredClaims: jwt.RegisteredClaims{
            Subject:   creds.Username,
            ExpiresAt: jwt.NewNumericDate(now.Add(2 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(now),
            Issuer:    "my-app",
            ID:        fmt.Sprintf("%d", now.UnixNano()),
        },
        Username: creds.Username,
        Role:     role,
    }

    // Генерируем токен
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString(secretKey)
    if err != nil {
        log.Printf("Error signing token: %v", err)
        http.Error(w, "Internal server error", http.StatusInternalServerError)
        return
    }

    // Возвращаем токен
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "token":   tokenString,
        "expires": claims.ExpiresAt.Time.Format(time.RFC3339),
    })
}

// Middleware для проверки JWT
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        if authHeader == "" {
            http.Error(w, "Missing authorization header", http.StatusUnauthorized)
            return
        }

        // Ожидаем формат "Bearer "
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
            return
        }

        tokenString := parts[1]

        // Парсим и верифицируем токен
        claims := &AppClaims{}
        token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
            // Проверяем метод подписи
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
            }
            return secretKey, nil
        })

        if err != nil {
            http.Error(w, "Invalid or expired token:                    

Рекомендаций