JWT认证中间件需用golang-jwt/jwt/v5封装:从Authorization头提取Bearer token,用ParseWithClaims解析并校验签名与过期时间,将自定义Claims(须嵌入jwt.RegisteredClaims)存入c,失败时AbortWithStatusJSON(401)。

JWT认证中间件怎么写
Gin 本身不内置 JWT 支持,得靠 github.com/golang-jwt/jwt/v5(v5 是当前主流)自己封装中间件。核心逻辑是:从请求头 Authorization: Bearer <token></token> 提取 token → 解析并校验签名、过期时间、issuer 等 → 把解析出的 claims(比如 user_id、role)塞进 c.Request.Context() 或直接存到 c 上供后续 handler 使用。
常见错误是没校验 err == nil 就直接断言 *jwt.Token,结果 panic;或者用错密钥类型(SigningMethodHS256 对应 []byte,不是字符串)。
实操建议:
- 用
jwt.ParseWithClaims(tokenString, &MyClaims{}, keyFunc),别用已弃用的Parse原始方法 -
keyFunc必须返回interface{},HS256 场景下直接返回mySecretKey([]byte)即可 - 在 claims 结构体里嵌入
jwt.RegisteredClaims,否则Valid()不生效,过期检查会失效 - 校验失败时统一返回
c.AbortWithStatusJSON(401, gin.H{"error": "invalid or expired token"}),别漏掉c.Abort()
怎么定义和验证自定义 Claims
不能只用 map[string]interface{},否则拿不到 ExpiresAt 自动校验能力。必须定义结构体,并显式嵌入 jwt.RegisteredClaims。
示例:
type MyClaims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
这样 token.Claims.(*MyClaims).Valid() 才会检查 exp、nbf 等字段。如果只写 struct{UserID uint},Valid() 永远返回 true,过期 token 也能通过。
注意点:
-
RegisteredClaims字段名是小写(如ExpiresAt),但 JSON tag 是大写("exp"),库内部自动映射,不用改 - 生成 token 时,要显式设置
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),别依赖默认值 - 测试时故意把系统时间调快,看是否真返回 401 —— 很多人跳过这步,上线后才发现过期逻辑没起作用
登录接口怎么签发 token 并返回
登录成功后,用 jwt.NewWithClaims(signingMethod, claims) 创建 token 实例,再调 token.SignedString(key) 生成字符串。关键不是“怎么生成”,而是“怎么安全返回”。
实操建议:
- 不要把 token 放在响应 body 里就完事,前端还得手动存 localStorage —— 更推荐同时设
HttpOnlycookie(需前后端同域)或明确告诉前端存哪 - cookie 方式示例:
c.SetCookie("access_token", tokenString, 3600, "/", "example.com", false, true),其中最后两个参数分别是Secure和HttpOnly - 如果走 Header 返回(如
X-Access-Token),前端必须手动处理存储和后续携带,容易漏;不如直接返回{"token": "xxx"}让前端自己决定 - 密码比对务必用
bcrypt.CompareHashAndPassword,别用==直接比较明文或哈希值
为什么路由加了 Auth 中间件还是 401
最常见三个原因:Header 格式不对、密钥不一致、时间偏差过大。
排查顺序:
- 用 curl 测试:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." http://localhost:8080/api/profile,确认 token 是完整字符串(不含换行或空格) - 检查中间件里
keyFunc返回的密钥是否和签发时完全一致(包括类型:[]byte("my_secret")≠"my_secret") - 服务器时间不准会导致
exp校验失败,用date命令核对,或临时把jwt.WithValidator替换为宽松校验(仅调试用) - Gin 路由组注册顺序错乱:比如
r := gin.Default(); r.Use(AuthMiddleware())写在r.POST("/login", LoginHandler)之后,导致 login 接口也被拦截
JWT 本身无状态,但密钥轮换、黑名单、提前吊销这些事得自己补 —— 别以为加了中间件就万事大吉。token 一旦发出,除非过期,服务端无法单方面让它失效。


















