4. JWT 原理与实现

JWT(JSON Web Token)是目前分布式系统中最主流的无状态身份认证方案。在 Go 业务模块的边界权限验证中,JWT 承担着“通行证”的角色:用户登录后获得一个签发的 Token,后续每次请求携带该 Token,服务端无需查询会话存储即可完成身份与权限校验。

4.1 JWT 结构:三部分拆解

一个标准的 JWT 由三个 Base64URL 编码的片段组成,以点号(.)分隔:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
部分 名称 内容说明 是否加密
Header 头部 令牌类型(typ)和签名算法(alg),如 HS256、RS256 仅 Base64 编码,不加密
Payload 载荷 声明(claims):用户标识、角色、过期时间等业务数据 仅 Base64 编码,不加密
Signature 签名 对 Header + Payload 使用密钥和指定算法生成的签名 防篡改
关键认知:JWT 默认是签名验证而非加密。Payload 中的敏感信息(如密码、身份证号)不应直接放入,必须使用时需额外加密。
JWT 三部分结构 Header {"alg":"HS256"} {"typ":"JWT"} Payload {"sub":"user123"} {"role":"admin"} Signature HMACSHA256( base64(Header)+"."+base64(Payload), secret) 最终 Token 格式 base64(Header) . base64(Payload) . Signature 三部分以点号连接,任何对 Header 或 Payload 的篡改都会导致签名验证失败

4.2 生成 Token:签名流程

生成 JWT 的核心步骤:构造 Header → 构造 Payload → 使用密钥签名。Go 中推荐使用 github.com/golang-jwt/jwt/v5 库(原 dgrijalva/jwt-go 的官方维护分支)。

4.2.1 安装与导入

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

4.2.2 生成 Token 代码示例

package main

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

// 自定义 Claims 结构体
type CustomClaims struct {
    UserID string   `json:"user_id"`
    Roles  []string `json:"roles"`
    jwt.RegisteredClaims
}

func GenerateToken(secret string) (string, error) {
    claims := CustomClaims{
        UserID: "u_10086",
        Roles:  []string{"admin", "editor"},
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), // 过期时间
            IssuedAt:  jwt.NewNumericDate(time.Now()),                    // 签发时间
            Issuer:    "go-auth-system",                                  // 签发者
            Subject:   "access_token",                                    // 主题
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString([]byte(secret))
    if err != nil {
        return "", err
    }
    return tokenString, nil
}

func main() {
    secret := "my-256-bit-secret-key"
    token, _ := GenerateToken(secret)
    fmt.Println("Generated JWT:", token)
}
最佳实践:密钥长度必须与算法匹配。HS256 要求密钥至少 256 位(32 字节);生产环境密钥应从环境变量或密钥管理服务获取,禁止硬编码。

4.3 解析与验证 Token

验证 Token 时,服务端需要完成:签名校验 → 过期时间检查 → 声明提取。使用 jwt-go 的 ParseWithClaims 方法可以一步完成。

4.3.1 验证代码示例

func ValidateToken(tokenString string, secret string) (*CustomClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        // 1. 校验签名算法是否匹配
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return []byte(secret), nil
    })

    if err != nil {
        return nil, err
    }

    // 2. 类型断言提取 Claims
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        return claims, nil
    }

    return nil, fmt.Errorf("invalid token")
}

func main() {
    secret := "my-256-bit-secret-key"
    tokenStr := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

    claims, err := ValidateToken(tokenStr, secret)
    if err != nil {
        fmt.Println("Token validation failed:", err)
        return
    }
    fmt.Printf("Valid! UserID: %s, Roles: %v\n", claims.UserID, claims.Roles)
}

4.3.2 常见验证错误处理

错误类型 错误原因 处理方式
jwt.ErrSignatureInvalid 签名不匹配,Token 被篡改 返回 401,拒绝请求
jwt.ErrTokenExpired Token 已超过 ExpiresAt 时间 返回 401,提示重新登录或使用 Refresh Token
jwt.ErrTokenNotValidYet Token 的 NotBefore 时间未到 返回 401,等待 Token 生效
jwt.ErrTokenMalformed Token 格式错误(非三段式) 返回 400,拒绝解析
JWT 验证流程 接收 Token 分割 Header.Payload.Signature 签名校验(HMAC/RSA) 401 拒绝 过期时间 & 声明提取 验证要点 • 算法白名单校验 • 签名必须匹配 • 过期时间 > 当前时间

4.4 jwt-go 库核心 API 速查

API 功能 使用场景
jwt.NewWithClaims(method, claims) 创建 Token 对象,指定签名算法和 Claims 生成 Token
token.SignedString(key) 使用密钥对 Token 签名,返回完整 JWT 字符串 生成 Token
jwt.ParseWithClaims(tokenStr, claims, keyFunc) 解析并验证 Token,返回 *Token 和错误 验证 Token
jwt.NewNumericDate(t) 将 time.Time 转换为 JWT 标准时间格式 设置过期/签发时间
jwt.SigningMethodHS256 HMAC-SHA256 对称签名方法 单服务/内网信任环境
jwt.SigningMethodRS256 RSA-SHA256 非对称签名方法 微服务/跨系统签名分发
安全警告:切勿在 Payload 中存储明文密码、信用卡号等敏感信息。JWT 的 Payload 仅做 Base64 编码,任何持有 Token 的人都可以解码读取。如需加密载荷,请使用 JWE(JSON Web Encryption)标准。

4.5 对称 vs 非对称签名选择

特性 HS256(对称) RS256(非对称)
密钥 单一密钥,签名和验证使用同一个 私钥签名,公钥验证
性能 快(HMAC 计算量小) 慢(RSA 计算量大)
密钥分发 所有验证方必须持有密钥,泄露风险高 仅签名方持有私钥,公钥可公开分发
适用场景 单体应用、内网服务间信任 微服务网关、第三方开放 API

在 Go 业务模块边界权限验证中,推荐优先使用 RS256:认证中心持有私钥签发 Token,各业务模块只持有公钥验证,即使某个模块被攻破,攻击者也无法伪造 Token。