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 有差异(错误类型、校验选项等),升级前看官方迁移文档。
小结
签发与校验互为镜像:签发把声明+过期时间签进去,校验验签后把用户信息交给后续中间件。下一章就把「解析 + 角色校验」做成鉴权中间件。