Token失效拦截必须在中间件中解析并校验JWT,需提取Authorization Header中的Bearer token,调用jwt.ParseWithClaims验证签名、exp、nbf等字段,区分过期与签名错误,统一返回401响应。

Token失效拦截必须在中间件里解析并校验 JWT
Token 失效不能靠前端传参或路径前缀判断,必须在中间件中真实解析 Authorization Header 里的 JWT,并验证签名、过期时间(exp)、生效时间(nbf)等字段。Gin 自身不提供 JWT 解析能力,得用 github.com/golang-jwt/jwt/v5 或类似库手动处理。
常见错误是只检查 token 是否存在,或仅比对字符串长度,结果导致伪造的、过期的、篡改过的 token 也能通过。
- 必须调用
token.Valid判断整体有效性,不能只读Claims就认为合法 - 过期错误(
jwt.ErrTokenExpired)和签名错误(jwt.ErrTokenUnverifiable)要区分返回,前者可提示“登录已过期”,后者应视为非法请求直接拒掉 - 解析失败时不要 panic,要用
c.AbortWithStatusJSON(401, ...)统一响应,避免触发 recovery 中间件造成日志污染
如何从 Context 提取并验证 Token 字符串
标准 Bearer 格式是 Authorization: Bearer xxxxx,中间件需先提取 token 字符串,再解码验证。注意空格、大小写、缺失前缀等边界情况。
示例关键逻辑:
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(401, gin.H{"code": 40001, "msg": "缺少 Authorization 头"})
return
}
var tokenString string
if strings.HasPrefix(authHeader, "Bearer ") {
tokenString = strings.TrimPrefix(authHeader, "Bearer ")
} else {
c.AbortWithStatusJSON(401, gin.H{"code": 40002, "msg": "Authorization 格式错误,需为 Bearer token"})
return
}
// 后续用 jwt.ParseWithClaims 解析...
- 别用
c.Query("token")或c.Param("token")替代 Header,不符合 REST 规范且易被绕过 - 提取后建议做基础长度校验(如
len(tokenString) 直接拒绝),减少无效解析开销 - JWT 密钥(
secret)必须从环境变量或配置中心加载,禁止硬编码在代码里
拦截后如何安全传递用户信息给后续 Handler
验证通过后,要把用户 ID、角色、权限等信息存进 c,供下游业务逻辑使用。不能用全局变量或闭包捕获,必须走 c.Set()。
典型做法:
claims := token.Claims.(jwt.MapClaims)
userID := uint64(claims["user_id"].(float64))
c.Set("user_id", userID)
c.Set("role", claims["role"])
c.Next() // 允许继续执行
- 务必在
c.Next()前完成c.Set(),否则 handler 里c.MustGet("user_id")会 panic - 避免直接存原始
claimsmap,容易引发类型断言失败;建议转成结构体或只存必要字段 - 如果 handler 需要强类型用户对象,可在中间件末尾调用
c.Set("user", &User{...}),但注意不要重复初始化
为什么分组绑定中间件比全局 Use() 更合理
不是所有路由都需要 Token 校验,比如 /login、/register、/healthz。强行全局挂载会导致未登录接口也触发解析逻辑,浪费 CPU 且掩盖真实问题。
正确方式是按业务域分组,例如:
api := r.Group("/api")
{
api.POST("/login", LoginHandler)
api.POST("/register", RegisterHandler)
v1 := api.Group("/v1")
v1.Use(AuthMiddleware) // 仅 /api/v1/ 下所有路由受保护
{
v1.GET("/users", ListUsersHandler)
v1.POST("/orders", CreateOrderHandler)
}
}
- 分组路径前缀自动合并,
v1.GET("/users")对应真实路径/api/v1/users,不会多出斜杠 - 中间件只作用于该分组内注册的路由,不影响
/api/login这类开放接口 - 多个分组可复用同一中间件函数,但各自独立执行,互不干扰
最易被忽略的一点:AuthMiddleware 内部必须显式调用 c.Abort() 或 c.AbortWithStatusJSON() 来终止链路,否则即使 token 失效,请求仍会穿透到 handler,造成越权访问。


















