JWT鉴权

JWT 是什么

JSON Web Token(RFC 7519)是一种紧凑的、自包含的令牌格式:把用户信息放进 JSON(Payload),用数字签名保证完整性(数据没被篡改)。核心优势:

  • 无状态:服务端不存会话,天然支持分布式部署和水平扩展;
  • 自包含:用户信息就在 token 里,验签通过即信任;
  • 跨域友好:可在多服务/多域间传递。

组成:Header.Payload.Signature

header.payload.signature
  • Header:算法和类型,如 {"alg":"HS256","typ":"JWT"}
  • Payload:三类声明——注册声明(exp/iss/aud/sub)、公共声明、私有声明(如 user_id);
  • Signature:对 header.payload 的签名。HS256 的计算:
HMACSHA256(base64Url(header) + "." + base64Url(payload), secret)

⚠️ Payload 只是 Base64 编码,不是加密——任何人都能解码看到内容,所以绝不能放密码等敏感信息。安全性来自签名(防篡改),不来自保密。

Go 实现(golang-jwt/jwt/v5)

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

生成 Token

type CustomClaims struct {
    UserID int `json:"user_id"`
    jwt.RegisteredClaims
}

func GenerateToken(userID int, secret string) (string, error) {
    claims := CustomClaims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            Issuer:    "myapp",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(secret))
}

解析与验证

func ValidateToken(tokenString, secret string) (*CustomClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{},
        func(t *jwt.Token) (interface{}, error) {
            // 关键:校验签名算法,防止算法混淆攻击(alg=none/RS256→HS256)
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
            }
            return []byte(secret), nil
        })
    if err != nil { return nil, err }
    claims, ok := token.Claims.(*CustomClaims)
    if !ok || !token.Valid { return nil, errors.New("invalid token") }
    return claims, nil
}

Gin 中间件集成

func JWTAuthMiddleware(secret string) gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "未提供Token"})
            return
        }
        // 习惯上客户端带 "Bearer xxx",这里要剥掉前缀
        tokenString = strings.TrimPrefix(tokenString, "Bearer ")

        claims, err := ValidateToken(tokenString, secret)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "无效Token"})
            return
        }
        c.Set("user_id", claims.UserID)
        c.Next()
    }
}

安全实践

  • 密钥:至少 256 位强随机密钥,放环境变量/密钥管理服务,绝不硬编码;定期轮换;
  • 有效期:access token 设短(15min–24h),配合 Refresh Token 续期;
  • 传输:必须 HTTPS;
  • 主动失效:JWT 无法主动作废——登出/改密/封号需要黑名单(Redis 存 jti/version)或短过期策略;
  • 校验 iss/aud:防止跨服务乱用 token;
  • 算法白名单:验证时必须检查签名算法(见上面注释),防算法混淆攻击。

总结

JWT 用”签名保证完整 + 无状态换取扩展”,在微服务和分布式场景是标配。实现上关注三件事:密钥管理、有效期与刷新、主动失效机制。高安全场景可换 RS256(非对称,私钥签发、公钥验证,适合多服务共享验证)。

滚动至顶部