Skip to content

配置详解

Config 是 CyberGo JWT 的统一配置入口。本页聚焦签名算法之外的安全与行为配置字段;签名密钥与算法选择见 签名算法

配置概览

DefaultConfig() 提供合理的默认值,只需设置密钥即可使用:

字段默认值说明
AccessTokenTTL15 分钟访问令牌有效期
RefreshTokenTTL7 天刷新令牌有效期
Issuer"jwt-service"写入 iss 声明并校验
SigningMethodHS256签名算法
ClockSkew0时钟偏移容忍
RequireExpirationfalse是否强制要求 exp 声明
ExpectedAudience""(不校验)预期受众

normalizeConfig 自动填充规则

New() 在校验前调用 normalizeConfig,对零值字段填充默认值。以下表格列出每条规则:

零值条件填充的默认值触发条件
AccessTokenTTL == 015 分钟总是
RefreshTokenTTL == 07 天总是
Issuer == """jwt-service"总是
SigningMethod == ""HS256总是
RateLimitRate == 0100EnableRateLimit == true
RateLimitWindow == 01 分钟EnableRateLimit == true
Blacklist.MaxSize == 0100000仅内置存储(Store == nil
Blacklist.CleanupInterval == 05 分钟仅内置存储
Blacklist.EnableAutoCleanup强制 true仅内置存储

何时触发速率限制默认值

RateLimitRateRateLimitWindow 的默认值仅在 EnableRateLimittrue填充。若 EnableRateLimitfalse(默认),速率限制不启用,这两个字段被忽略。详见 速率限制

自定义 BlacklistStore 跳过填充

Blacklist.Store 不为 nil(使用自定义存储后端)时,MaxSizeCleanupIntervalEnableAutoCleanup 三个字段均被忽略——存储管理由后端自行负责。内置存储的 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)

ClockSkewexp(过期)和 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 = 30s ~ 60s。零值(默认)表示严格校验,不施加任何宽容。

ClockSkew 对令牌有效性的影响

以下表格展示 ClockSkew = 30s 时,一个 exp = 12:00:00nbf = 12:00:00 的令牌在各验证时间点的有效性:

验证时间与 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 的旧令牌。生产环境建议启用。

令牌 TTL 设计

访问令牌和刷新令牌的 TTL 应根据业务场景平衡安全与体验:

场景AccessTokenTTLRefreshTokenTTL说明
高安全(金融、医疗)5 分钟1 小时短 TTL 限制暴露窗口
Web 应用15 分钟7 天默认值,平衡安全与体验
移动端 App30 分钟30 天较长 TTL 减少重新登录
内部服务1 小时24 小时内网信任度较高

约束

Config.Validate() 要求 AccessTokenTTL < RefreshTokenTTL,且两者必须为正数。

配置校验矩阵

Config.Validate()New() 中于 normalizeConfig 之后执行,返回三类错误:ErrInvalidConfigErrInvalidSecretKeyErrInvalidSigningMethod

签名密钥校验(按算法)

算法族SigningKey 要求VerificationKey(可选)
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 签名密钥强度< 2048ErrInvalidSecretKey
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
TTL 关系AccessTokenTTL >= RefreshTokenTTLErrInvalidConfig
ClockSkew< 0ErrInvalidConfig
Blacklist MaxSize<= 0(仅内置存储)ErrInvalidConfig
Blacklist CleanupInterval<= 0(仅内置存储)ErrInvalidConfig

校验顺序

Validate() 先校验签名密钥(返回 ErrInvalidSecretKeyErrInvalidSigningMethod),再校验 TTL、ClockSkew 和 Blacklist 配置(返回 ErrInvalidConfig)。若密钥不合法,后续检查不会执行——修复第一个报错后重新测试。

输入校验与安全加固

CyberGo JWT 对 Claims 字段实施多层输入校验,防止注入攻击和异常数据。

字段约束

校验项限制触发错误
字符串字段长度≤ 256 字符ValidationError
数组大小(permissions、scopes、audience)≤ 100 项ValidationError
Extra 字段数量≤ 50 个ValidationError
Extra 值类型string[]stringValidationError(拒绝嵌套 map)

受校验的字符串字段包括 UserIDUsernameRoleSessionIDClientID 以及 RegisteredClaims 中的 IssuerSubjectIDTokenType

注入模式检测

库内置 46 种危险模式检测,覆盖 XSS、SQL 注入、路径穿越等攻击向量:

  • XSS<script>javascript:onerror=<iframe> 等 HTML/JS 注入标签
  • SQL 注入drop tableunion select
  • 路径穿越..//etc/passwdfile://
  • 控制字符:除 Tab(9)、换行(10)、回车(13)外的 ASCII < 32 字符

检测到危险模式时返回 ValidationErrorField 为字段名,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.Iserrors.As 链式查找底层错误。在 CreateValidate 路径中,校验错误会被 ErrInvalidClaims 包装。

自定义 Claims 校验

实现 CustomClaims 接口的类型,其自定义字段不会被深度校验——实现者需在 Validate() 方法中自行处理。标准 JWT 字段(isssubjti 等)的长度和注入校验始终执行。详见 自定义 Claims

下一步