логотип PurpleSchool
логотип PurpleSchool

Работа с JSON Web Tokens в Go

Автор

Александр Гольцман

JSON Web Token (JWT) — это компактный формат для безопасной передачи информации между участниками. Он широко используется для аутентификации и авторизации в веб-приложениях. В этой статье я расскажу, как работает JWT, какие у него есть особенности и как его использовать в Go. Мы рассмотрим структуру токена, разберём основные алгоритмы подписи и покажем примеры работы с JWT на практике.

Что такое JSON Web Token (JWT)?

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

Токен состоит из трёх частей:

  1. Header (заголовок) — содержит информацию о типе токена и алгоритме подписи.
  2. Payload (полезная нагрузка) — включает в себя данные (claims), например, идентификатор пользователя или срок действия токена.
  3. Signature (подпись) — используется для проверки целостности токена.

Смотрите, как выглядит структура JWT в кодированном виде:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsImV4cCI6MTcwOTYwMDAwMH0.3QKxL9Ae2qLZ6a2U6wNOfGQX6qZp8WbD1o0X6K8X7gU

Эта строка состоит из трёх частей, разделённых точками. Каждая часть закодирована в Base64. Декодировав её, можно увидеть JSON-объект с заголовком, данными и подписью.

Как работает JWT?

  1. Клиент (обычно браузер или мобильное приложение) отправляет серверу запрос с логином и паролем.
  2. Сервер проверяет данные и, если они верны, создаёт JWT, содержащий информацию о пользователе.
  3. Токен отправляется клиенту, который сохраняет его (например, в localStorage или HTTP cookie).
  4. При каждом запросе клиент отправляет токен в заголовке Authorization: Bearer <токен>.
  5. Сервер проверяет токен и, если он действителен, выполняет запрос.

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

Использование JWT в Go

В Go есть несколько библиотек для работы с JWT, например, github.com/golang-jwt/jwt.

Создание JWT

Вот пример генерации JWT-токена в Go:

package main

import (
    "fmt"
    "time"

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

var secretKey = []byte("my_secret_key")

func generateToken(userID int) (string, error) {
    claims := jwt.MapClaims{
        "user_id": userID,
        "exp":     time.Now().Add(time.Hour * 24).Unix(), // Срок действия — 24 часа
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secretKey)
}

func main() {
    token, err := generateToken(123)
    if err != nil {
        fmt.Println("Ошибка генерации токена:", err)
        return
    }
    fmt.Println("JWT:", token)
}

Здесь я создал токен с user_id и временем истечения. Он подписан с помощью HS256 и секретного ключа.

Проверка JWT

Смотрите, как можно декодировать и проверить токен:

func parseToken(tokenString string) (*jwt.Token, error) {
    return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        return secretKey, nil
    })
}

Если подпись верна, можно получить claims и извлечь из них данные.

Использование JWT в HTTP-запросах

JWT обычно передаётся в заголовке Authorization. Смотрите, как можно извлекать и проверять токен в HTTP-хендлере:

package main

import (
    "fmt"
    "net/http"
    "strings"

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

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, "Токен не предоставлен", http.StatusUnauthorized)
            return
        }

        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        token, err := parseToken(tokenString)
        if err != nil || !token.Valid {
            http.Error(w, "Неверный токен", http.StatusUnauthorized)
            return
        }

        next(w, r)
    }
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Вы получили доступ!")
}

func main() {
    http.HandleFunc("/protected", authMiddleware(protectedHandler))
    http.ListenAndServe(":8080", nil)
}

Здесь я реализовал middleware, который проверяет JWT перед выполнением запроса.

Срок действия и обновление токена

JWT может иметь срок действия (exp), после которого он становится недействительным. При истечении срока клиент должен обновить токен, например, через refresh-токен.

Обычно это реализуется так:

  1. Клиент получает access-токен и refresh-токен.
  2. Когда access-токен истекает, клиент отправляет refresh-токен на сервер.
  3. Сервер проверяет refresh-токен и, если он действителен, выдаёт новый access-токен.

Этот механизм предотвращает постоянный запрос логина и пароля у пользователя.

Безопасность JWT

Несколько важных рекомендаций по безопасности:

  • Используйте безопасные алгоритмы подписи (например, HS256, RS256).
  • Храните секретные ключи в защищённом месте (например, в переменных окружения).
  • Не храните чувствительные данные в payload, так как его можно декодировать.
  • Устанавливайте срок действия токенов, чтобы ограничить их жизненный цикл.
  • Используйте refresh-токены вместо продления старых access-токенов.

Если нужно, чтобы токен нельзя было подделать даже при компрометации ключа, можно использовать шифрование (JWE), но это усложнит обработку.

Заключение

JSON Web Token — мощный инструмент для аутентификации и авторизации. Он позволяет передавать данные между клиентом и сервером без хранения сессий на сервере. В Go JWT легко реализуется с помощью пакета github.com/golang-jwt/jwt.

Давайте подведём итоги:

  • JWT состоит из заголовка, полезной нагрузки и подписи.
  • Используется для передачи информации между клиентом и сервером без необходимости хранить состояние на сервере.
  • Подписывается с помощью алгоритмов HMAC или RSA для защиты от подделки.
  • Содержит срок действия, что предотвращает несанкционированное использование.
  • В реальных проектах важно правильно настраивать срок жизни токена и защищать его.

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

Стрелочка влевоРабота с Redis в GoГенерация и работа с UUID в GolangСтрелочка вправо

Все гайды по Golang

Работа с YAML в GolangПреобразование типов в GolangКонвертация структур в JSON в GolangStrconv в GolangИспользование пакета SQLx для работы с базами данных в GolangРазбираемся с SQL в GolangРазделение строк с помощью функции split в GolangSort в GoПоиск и замена строк в Go - GolangИспользование пакета reflect в GolangРабота с PostgreSQL в GoPointers в GolangПарсинг в GoРабота со списками (list) в GolangПреобразование int в string в GolangРабота с числами с плавающей точкой в GolangРабота с полями в GolangИспользование enum в GolangОбработка JSON в GoЧтение и запись CSV-файлов в GolangРабота с cookie в GolangРегистры в GoКэширование данных в GolangПреобразование byte в string в GolangByte в GoИспользование bufio для работы с потоками данных в GolangДобавление данных и элементов (add) в Go
Логирование в Golang. Zap, Logrus, Loki, GrafanaРабота с Docker-контейнерами в GoИспользование pprof в GolangМеханизмы синхронизации в GolangРабота с пакетом S3 в GolangМониторинг Golang приложений с помощью PrometheusОптимизация проектов на GoПаттерны проектирования в GolangМиграции базы данных в GolangОркестрация контейнеров Go с Kubernetes + DockerGjGo Playground и компилятор GolangИспользование go mod init для создания модулей GolangРабота с переменными окружения (env) в GolangКоманда go build в GolangАвтоматизация Golang проектов — CI/CD с GitLab CI и JenkinsОтладка кода в GolangЧтение и использование конфигурации в приложениях на GolangКомпиляция в GolangКак развернуть Go-приложение на облаке AWSАутентификация в Golang
Сетевые протоколы в GoПеременные в GolangЗначения в GolangДженерик %T и его применение в GolangТипы данных в GolangИспользование tls в GolangИспользование tag в структурах GolangSwitch в GoСтроки в GolangРабота с потоками (stream) в GolangSelect в GoРуны в GoРабота с пакетом params в GolangКонвертация строк в числа в GolangNull, Nil, None, 0 в GoНаименования переменных, функций и структур в GoInt в GolangУстановка GolangЧтение и установка HTTP заголовков в GolangMethods в GolangGoLand — IDE для разработки на Golang от JetBrainsОбработка «not found» в GolangFloat в GolangФлаги командной строки в Go (Golang)Запуск внешних команд в GolangОбработка ошибок в GoИспользование defer в GolangЗначения default в GolangГенерация кода в GoФорматирование кода в GolangЧистая архитектура в GolangКаналы (channels) в GolangПолучение body из HTTP запроса в Golang
Открыть базу знаний