★ 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。