★ JWT 认证:签发/校验/中间件落地

结论先行

JWT = header.payload.signature 三段 Base64URL 拼接,无状态、可自校验。Gin 不内置 JWT,社区主流是 github.com/golang-jwt/jwt/v5(也可用 gin-contrib/jwt 封装)。落地三步:签发(服务端用密钥签名 claims)、校验(验签 + 算法白名单 + exp 有效期)、中间件(解析 Authorization: Bearer,把用户信息塞进 c)。

核心概念

概念说明
Header{“alg”:“HS256”,“typ”:“JWT”},声明算法
Payload注册 claims(iss/sub/exp/iat/nbf)+ 自定义 claims
Signature用密钥对 header.payload 签名,防篡改
HS256对称:签发/校验同一把 secret,单服务或内网用
RS256/ES256非对称:私钥签、公钥验,多服务验签只持公钥
生命周期服务端不存会话 → 天然支持水平扩展;代价是无法真正“踢人”(需黑名单/短 token)

代码示例

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

type Claims struct {
    UserID string `json:"uid"`
    jwt.RegisteredClaims
}

var hmacKey = []byte("请替换为>=32字节随机密钥")

func sign(uid string) (string, error) {
    claims := Claims{
        UserID: uid,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer:    "my-app",
            Subject:   uid,
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
        },
    }
    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(hmacKey)
}

func jwtAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        h := c.GetHeader("Authorization")
        tokenStr, ok := strings.CutPrefix(h, "Bearer ")
        if !ok || tokenStr == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
            return
        }
        tok, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
            // 算法白名单:防止把 RS256 等换成 HS256 的“算法混淆”攻击
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, errors.New("unexpected signing method")
            }
            return hmacKey, nil
        })
        if err != nil || !tok.Valid {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            return
        }
        c.Set("uid", tok.Claims.(*Claims).UserID) // 后续 handler 用 c.GetString("uid")
        c.Next()
    }
}

落地注意

  • jwt/v5 解析时 exp/nbf 存在会自动校验(也支持 WithValidMethods/WithExpirationRequired 等选项,以库文档为准)。
  • 密钥强度:HS256 建议 ≥32 字节并支持轮换;泄露 = 全员 token 可伪造。
  • 敏感数据别放 claims(payload 只是 Base64,不是加密);需要防窥视用 JWE 或落库。
  • 登出/封禁:无状态 token 无法单点失效,方案是黑名单(Redis)或短 access token + refresh token。
  • 公开接口(登录、健康检查)不要挂 jwtAuth:用分组隔离(见第 16 章)。

常见追问 / 记忆点

  • 追问:HS256 和 RS256 怎么选?答:单服务/内网用 HS256 简单;多服务共享验签或第三方要验签用 RS256(只分发公钥)。
  • 追问:JWT 和 Session 区别?答:JWT 无状态、易水平扩展,但难吊销、体积随 claims 变大;Session 可服务端吊销,但要共享存储。
  • 记忆点:签发 sign → 校验 parse(算法白名单 + exp)→ 中间件 401 + 塞 c;密钥藏好、敏感数据别进 payload。
笔记加载中…