Skip to content

Подробная конфигурация

Config — единая точка входа для конфигурации CyberGo JWT. Эта страница посвящена полям безопасности и поведения помимо алгоритмов подписи; ключи и выбор алгоритма см. в Алгоритмы подписи.

Обзор конфигурации

DefaultConfig() предоставляет разумные значения по умолчанию — достаточно задать секретный ключ:

ПолеПо умолчаниюОписание
AccessTokenTTL15 минутВремя жизни access-токена
RefreshTokenTTL7 днейВремя жизни refresh-токена
Issuer"jwt-service"Записывается в iss и проверяется
SigningMethodHS256Алгоритм подписи
ClockSkew0Допуск часов
RequireExpirationfalseТребуется ли утверждение exp
ExpectedAudience"" (без проверки)Ожидаемая аудитория

Правила автозаполнения normalizeConfig

New() перед проверкой вызывает normalizeConfig, заполняющий нулевые поля значениями по умолчанию. Следующая таблица описывает каждое правило:

Нулевое условиеЗаполняемое значениеУсловие срабатывания
AccessTokenTTL == 015 минутВсегда
RefreshTokenTTL == 07 днейВсегда
Issuer == """jwt-service"Всегда
SigningMethod == ""HS256Всегда
RateLimitRate == 0100Только при EnableRateLimit == true
RateLimitWindow == 01 минутаТолько при EnableRateLimit == true
Blacklist.MaxSize == 0100000Только для встроенного хранилища (Store == nil)
Blacklist.CleanupInterval == 05 минутТолько для встроенного хранилища
Blacklist.EnableAutoCleanupПринудительно trueТолько для встроенного хранилища

Когда срабатывают значения по умолчанию для ограничения скорости

Значения по умолчанию RateLimitRate и RateLimitWindow заполняются только когда EnableRateLimit равно true. Если EnableRateLimit равно false (по умолчанию), ограничение скорости не включается и эти поля игнорируются. См. Ограничение скорости.

Пользовательский BlacklistStore пропускает заполнение

Когда Blacklist.Store не равно nil (используется пользовательский бэкенд хранилища), поля MaxSize, CleanupInterval и EnableAutoCleanup игнорируются — управление хранилищем берёт на себя бэкенд. EnableAutoCleanup встроенного хранилища принудительно устанавливается в true для предотвращения неограниченного роста памяти.

Проверка издателя и аудитории

Issuer (Издатель)

При заданном Issuer он записывается в утверждение iss при создании токена и проверяется на соответствие при верификации:

go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.Issuer = "my-app-v1" // Токен будет содержать iss: "my-app-v1"

При проверке, если iss токена не соответствует настроенному значению, возвращается ErrTokenInvalidIssuer.

ExpectedAudience (Ожидаемая аудитория)

При заданном ExpectedAudience проверка удостоверяется, что утверждение aud токена содержит это значение:

go
package main

import (
    "fmt"
    "time"

    "github.com/cybergodev/jwt"
)

func main() {
    cfg := jwt.DefaultConfig()
    cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
    cfg.ExpectedAudience = "billing-api"

    processor, err := jwt.New(cfg)
    if err != nil {
        panic(err)
    }
    defer processor.Close()

    // Токен с совпадающей аудиторией
    claims := &jwt.Claims{
        UserID: "user1",
        RegisteredClaims: jwt.RegisteredClaims{
            Audience: jwt.StringOrSlice{"billing-api"},
        },
    }
    token, err := processor.Create(claims)
    if err != nil {
        panic(err)
    }

    _, valid, _ := processor.Validate(token)
    fmt.Println("Valid:", valid)
    // Вывод: Valid: true

    // Токен с неверной аудиторией отклоняется
    wrongClaims := &jwt.Claims{
        UserID: "user2",
        RegisteredClaims: jwt.RegisteredClaims{
            Audience: jwt.StringOrSlice{"admin-api"},
        },
    }
    wrongToken, _ := processor.Create(wrongClaims)
    _, valid, _ = processor.Validate(wrongToken)
    fmt.Println("Wrong audience valid:", valid)
    // Вывод: Wrong audience valid: false
}

Сценарии с микросервисами

В микросервисных архитектурах задавайте разные ExpectedAudience для разных сервисов, чтобы токены, выпущенные одним сервисом, не принимались другим, обеспечивая межсервисную изоляцию токенов.

Допуск часов (ClockSkew)

ClockSkew предоставляет окно допустимости для проверки exp (истечение) и nbf (не ранее), компенсируя рассинхронизацию часов между издателем и проверяющим. Допуск симметрично действует на оба временных утверждения:

  • В направлении exp: токен считается истёкшим только после exp + ClockSkew — смягчает проверку истечения
  • В направлении nbf: токен считается действительным уже с nbf - ClockSkew — смягчает проверку «не ранее»
go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.ClockSkew = 30 * time.Second // Допуск 30 секунд рассинхронизации

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

В распределённых системах рассинхронизация часов между серверами может составлять несколько секунд. Рекомендуется ClockSkew = 30с ~ 60с. Нулевое значение (по умолчанию) означает строгую проверку без допуска.

Влияние ClockSkew на действительность токена

Следующая таблица показывает действительность токена с exp = 12:00:00, nbf = 12:00:00 при ClockSkew = 30s в различные моменты проверки:

Время проверкиОтношение к expОтношение к nbfРезультат
11:59:20Не истёкnbf - 40s (за пределами допуска)Недействителен: ErrTokenNotValidYet
11:59:40Не истёкnbf - 20s (в пределах допуска)Действителен
12:00:00Не истёкМомент nbfДействителен
12:00:10exp + 10s (в пределах допуска)Уже действителенДействителен
12:00:40exp + 40s (за пределами допуска)Уже действителенНедействителен: ErrTokenExpired

Допуск только расширяет, не сужает

ClockSkew только расширяет окно принятия токена, не сужая его по сравнению со строгой проверкой. Нулевое значение эквивалентно строгой семантике RFC 7519: токен вступает в силу ровно с nbf и истекает ровно в exp.

ClockSkew не может быть отрицательным — Config.Validate() возвращает ErrInvalidConfig.

Обязательный срок (RequireExpiration)

По умолчанию (RequireExpiration = false) токены без утверждения exp никогда не истекают. Это допустимо по RFC 7519, но может быть проблемой безопасности в чувствительных сценариях.

Установка RequireExpiration = true отклоняет токены без exp при проверке:

go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.RequireExpiration = true // Отклонять токены без exp

Усиление безопасности

Токены, выпущенные этой библиотекой, всегда содержат exp (производный от TTL), поэтому RequireExpiration в первую очередь влияет на токены от других издателей или устаревшие токены без exp. Рекомендуется включать в production.

Дизайн TTL токенов

TTL access и refresh токенов должен балансировать безопасность и удобство в зависимости от сценария:

СценарийAccessTokenTTLRefreshTokenTTLОписание
Высокая безопасность (финансы, медицина)5 минут1 часКороткий TTL ограничивает окно доступа
Веб-приложение15 минут7 днейПо умолчанию, баланс безопасности и UX
Мобильное приложение30 минут30 днейДлинный TTL сокращает повторные входы
Внутренний сервис1 час24 часаВысокое доверие во внутренней сети

Ограничение

Config.Validate() требует AccessTokenTTL < RefreshTokenTTL, и оба должны быть положительными.

Матрица проверки конфигурации

Config.Validate() выполняется в New() после normalizeConfig и возвращает три категории ошибок: ErrInvalidConfig, ErrInvalidSecretKey, ErrInvalidSigningMethod.

Проверка ключа подписи (по семейству алгоритмов)

СемействоТребования к SigningKeyVerificationKey (необязательно)
HMAC (HS256/384/512)Строка SecretKey ≥ 32 байт + не слабый ключНеприменимо (HMAC симметричный)
RSA (RS/PS 256/384/512)*rsa.PrivateKey ≥ 2048 бит*rsa.PublicKey ≥ 2048 бит
ECDSA (ES256/384/512)*ecdsa.PrivateKey, кривая соответствует алгоритму*ecdsa.PublicKey

Назначение VerificationKey

После установки VerificationKey проверка токена использует публичный ключ, а не приватный — подходит для сервисов, которые только проверяют (например, серверы ресурсов). Если опущено, для проверки используется приватный ключ из SigningKey. Подробнее см. Алгоритмы подписи.

Полный перечень проверок Config.Validate()

ПроверкаУсловиеВозвращаемая ошибка
Указатель конфигурацииnilErrInvalidConfig
Длина HMAC-ключаSecretKey < 32 байтErrInvalidSecretKey
Сила HMAC-ключаСлабый ключ (низкая энтропия/сложность)ErrInvalidSecretKey
Тип ключа подписи RSAНе *rsa.PrivateKeyErrInvalidSecretKey
Сила ключа подписи RSA< 2048 битErrInvalidSecretKey
Тип ключа проверки RSAНе *rsa.PublicKey (если задан)ErrInvalidSecretKey
Сила ключа проверки RSA< 2048 бит (если задан)ErrInvalidSecretKey
Тип ключа подписи ECDSAНе *ecdsa.PrivateKeyErrInvalidSecretKey
Соответствие кривой ECDSAКривая не соответствует алгоритму (например, ES256 требует P-256)ErrInvalidSecretKey
Тип ключа проверки ECDSAНе *ecdsa.PublicKey (если задан)ErrInvalidSecretKey
Алгоритм подписиНе входит в 12 встроенныхErrInvalidSigningMethod
AccessTokenTTL<= 0ErrInvalidConfig
RefreshTokenTTL<= 0ErrInvalidConfig
Соотношение TTLAccessTokenTTL >= RefreshTokenTTLErrInvalidConfig
ClockSkew< 0ErrInvalidConfig
Blacklist MaxSize<= 0 (только встроенное хранилище)ErrInvalidConfig
Blacklist CleanupInterval<= 0 (только встроенное хранилище)ErrInvalidConfig

Порядок проверки

Validate() сначала проверяет ключ подписи (возвращая ErrInvalidSecretKey или ErrInvalidSigningMethod), затем TTL, ClockSkew и конфигурацию Blacklist (возвращая ErrInvalidConfig). Если ключ недействителен, последующие проверки не выполняются — исправьте первую ошибку и повторите.

Проверка ввода и усиление безопасности

CyberGo JWT применяет многоуровневую проверку ввода к полям Claims, предотвращая инъекционные атаки и аномальные данные.

Ограничения полей

ПроверкаОграничениеВызываемая ошибка
Длина строкового поля≤ 256 символовValidationError
Размер массива (permissions, scopes, audience)≤ 100 элементовValidationError
Количество Extra-полей≤ 50 полейValidationError
Типы значений Extrastring, []stringValidationError (вложенные map отклоняются)

Проверяемые строковые поля включают UserID, Username, Role, SessionID, ClientID и поля RegisteredClaims: Issuer, Subject, ID, TokenType.

Обнаружение инъекционных шаблонов

Библиотека включает 46 встроенных определений опасных шаблонов, охватывающих XSS, SQL-инъекции, обход пути и другие векторы атак:

  • XSS: <script>, javascript:, onerror=, <iframe> и другие HTML/JS инъекционные теги
  • SQL-инъекции: drop table, union select и т.д.
  • Обход пути: ../, /etc/passwd, file://
  • Управляющие символы: ASCII < 32 кроме Tab (9), перевода строки (10), возврата каретки (13)

При обнаружении опасного шаблона возвращается ValidationError с Field, равным имени поля, и Message, равным "suspicious pattern detected".

Обработка ошибок проверки

go
token, err := processor.Create(claims)
if err != nil {
    var ve *jwt.ValidationError
    if errors.As(err, &ve) {
        fmt.Printf("Поле: %s, Причина: %s\n", ve.Field, ve.Message)
        // Поле: user_id, Причина: suspicious pattern detected
    }
}

ValidationError реализует Unwrap(), позволяя errors.Is и errors.As обходить underlying ошибку. В путях Create и Validate ошибки проверки оборачиваются в ErrInvalidClaims.

Проверка пользовательских Claims

Типы, реализующие интерфейс CustomClaims, не проходят глубокую проверку пользовательских полей — реализующий должен обрабатывать это в методе Validate(). Стандартные JWT-поля (iss, sub, jti и др.) всегда проверяются на длину и инъекции. См. Пользовательские Claims.

Дальнейшие шаги