JWT 登录实践:签发/校验流程与无状态认证设计(手写或社区库)

JWT 让认证变成无状态:服务端不存会话,Token 自身携带用户声明并用签名防篡改。主流社区库是 golang-jwt(github.com/golang-jwt/jwt/v5),以下以其 API 为准;gin-jwt(appleboy/gin-jwt)把签发与中间件封装成插件,适合快速接入,用法见其官方文档。

1. 签发 Token

JWT 由三段组成:Header(算法)、Payload(声明)、Signature。用 HS256 对称签名时密钥要保密且足够长:

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

var secret = []byte("change-me-to-a-32-byte-random-secret")

type Claims struct {
    UserID uint   `json:"uid"`
    Role   string `json:"role"`
    jwt.RegisteredClaims // 标准声明:exp/iat/iss 等
}

func IssueToken(userID uint, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "demo-app",
        },
    }
    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
}

2. 校验 Token

ParseWithClaims 反解并验签;v5 会按声明自动校验 exp 是否过期(过期返回 ErrTokenExpired):

func ParseToken(s string) (*Claims, error) {
    claims := &Claims{}
    _, err := jwt.ParseWithClaims(s, claims, func(t *jwt.Token) (any, error) {
        return secret, nil // 返回用于验签的密钥
    }, jwt.WithValidMethods([]string{"HS256"})) // 锁定算法,防算法混淆
    return claims, err
}

3. 登录接口串起流程

r.POST("/api/login", func(c *gin.Context) {
    var in struct {
        Name string `json:"name" binding:"required"`
        Pwd  string `json:"pwd" binding:"required"`
    }
    if err := c.ShouldBindJSON(&in); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
        return
    }
    if in.Name != "admin" || in.Pwd != "secret" { // 真实项目应查库并比对 bcrypt
        c.JSON(http.StatusUnauthorized, gin.H{"error": "账号或密码错误"})
        return
    }
    tok, err := IssueToken(1, "admin")
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "签发失败"})
        return
    }
    c.JSON(http.StatusOK, gin.H{"token": tok, "type": "Bearer"})
})

无状态认证设计

  • 服务端不存会话,扩容友好;代价是无法主动吊销单个 Token。
  • 常见补偿:短过期(如 15 分钟)+ Refresh Token,或把 jti 写进 Redis 黑名单。
  • Token 放 Authorization: Bearer <token> 头而非 URL 参数;全程 HTTPS 传输。
  • 密钥走环境变量或密钥管理服务,绝不进代码仓库。

注意点

  • HS256 密钥建议至少 32 字节;一旦泄露等于可任意签发,必须轮换。
  • 不要信任 Token 里的字段做敏感判断,每次请求都要重新校验签名与过期时间。
  • v5 与 v4 的 API 有差异(错误类型、校验选项等),升级前看官方迁移文档。

小结

签发与校验互为镜像:签发把声明+过期时间签进去,校验验签后把用户信息交给后续中间件。下一章就把「解析 + 角色校验」做成鉴权中间件。

笔记加载中…