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.

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