JWT 认证中间件设计
JWT 认证的完整设计:access token + refresh token 双 token 策略、签名验证、中间件注入上下文、安全注意事项。
#type / concept
#status / growing
#tech / dev / backend
#resource / go
[!info] related notes
- 前置: JWT, Go HTTP 中间件
- 框架: Gin HTTP 框架
JWT 认证中间件设计
双 Token 策略
| Token | TTL | 存储 | 用途 |
|---|---|---|---|
| Access Token | 短(7天) | 前端内存/localStorage | API 请求认证 |
| Refresh Token | 长(30天) | 数据库 | 刷新 access token |
Access token 泄露的影响窗口是 TTL 时间。Refresh token 可以通过数据库黑名单立即失效。
Claims 设计
type Claims struct {
UserID uuid.UUID `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
UserID: 业务标识,handler 通过c.Get("user_id")获取RegisteredClaims: 标准字段(exp, iat, sub),jwt 库自动验证
签名验证
func Validate(cfg JWTConfig, tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{},
func(token *jwt.Token) (interface{}, error) {
// 验证签名方法,防止 alg 攻击
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(cfg.SecretKey), nil
},
)
// ...
}
关键安全检查:验证签名方法是 HMAC,防止攻击者把 alg 改成 “none” 绕过签名。
中间件
func AuthMiddleware(jwtConfig auth.JWTConfig) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
c.JSON(401, gin.H{"error": "unauthorized"})
c.Abort()
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := auth.Validate(jwtConfig, tokenString)
if err != nil {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Set("user_id", claims.UserID.String())
c.Set("email", claims.Email)
c.Next()
}
}
安全注意事项
- Secret Key 必须随机 — 生产环境用
openssl rand -hex 32 - 不要在 JWT 中存敏感信息 — JWT 可以被 base64 解码
- HTTPS 必须 — JWT 在传输中被截获可以重放
- Access Token 短期 — 泄露影响窗口小
- Refresh Token 可以注销 — 存数据库,服务端验证有效性