Gin默认不内置JWT支持,因其定位为轻量级HTTP框架,仅提供基础路由与中间件机制,认证方案需开发者按需组合实现。

为什么 Gin 默认不内置 JWT 支持
Gin 是轻量级 HTTP 框架,设计上刻意不绑定任何认证方案。它只提供 gin.Context 和中间件机制,JWT 的签发、解析、校验、过期处理都得自己组合实现。直接用第三方库(比如 github.com/golang-jwt/jwt/v5)是主流做法,但要注意 v4 和 v5 的 API 差异很大——v5 把 Parse 拆成 ParseWithClaims,且默认禁用 unsafe 算法,HS256 以外的算法必须显式注册。
如何正确签发带用户信息的 JWT Token
签发 Token 的核心是构造 claims 并调用 jwt.NewWithClaims。别直接把数据库查出的原始 user 结构体塞进去,要先映射到精简的 claims 结构体,避免敏感字段(如密码哈希)意外泄露。
- 定义 claims 结构体时嵌入
jwt.RegisteredClaims,并添加自定义字段(如UserID uint) - 密钥必须是 []byte 类型,建议从环境变量读取,不要硬编码;长度至少 32 字节,否则
HS256会报错key is not sufficiently long - 设置
ExpiresAt时用time.Now().Add(24 * time.Hour).Unix(),别用time.Now().Add(24 * time.Hour).UTC()后再转 Unix——时区处理错误会导致 Token 提前失效 - 调用
token.SignedString(key)后,返回给前端时建议加Bearer前缀,但后端校验时不依赖这个前缀,而是从Authorizationheader 中手动截取
type CustomClaims struct {
UserID uint `json:"user_id"`
jwt.RegisteredClaims
}
<p>claims := CustomClaims{
UserID: 123,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
如何在 Gin 中间件里安全校验 JWT
校验不是“解析成功就放行”,必须检查三件事:签名有效性、过期时间、关键字段是否存在。Gin 中间件里常犯的错是忽略 err 类型判断——jwt.ErrTokenExpired 和 jwt.ErrTokenInvalid 处理方式不同,前者可返回 401,后者可能要记录异常请求。
- 从
c.Request.Header.Get("Authorization")取值后,用strings.HasPrefix(auth, "Bearer ")判断,再strings.TrimPrefix(auth, "Bearer ")提取 token 字符串 - 用
jwt.ParseWithClaims(tokenString, &CustomClaims{}, keyFunc),其中keyFunc必须返回和签发时一致的密钥,不能写死或返回 nil - 校验通过后,把解析出的
*CustomClaims存入c.Set("claims", claims),后续 handler 用c.MustGet("claims").(*CustomClaims)取值,别重复解析 - 如果 token 过期,
err是*jwt.TokenExpiredError,此时不应继续执行 handler,应直接c.AbortWithStatusJSON(401, gin.H{"error": "token expired"})
为什么 /login 接口不能加 JWT 中间件,而 /user/profile 必须加
这是权限控制的基本分层逻辑:登录本身是获取凭证的过程,加了 JWT 中间件反而会拦截所有未带 token 的请求,导致根本无法登录。而 /user/profile 这类接口需要已认证用户上下文,必须强制校验。实际项目中容易漏掉的是「刷新 Token」接口——它需要旧 token 解析出用户 ID,再签发新 token,所以既要校验旧 token,又不能要求新 token 已存在。
- 登录路由(如
POST /auth/login)必须独立注册,不挂任何认证中间件 - 受保护路由组统一用
router.Use(JWTAuthMiddleware()),但注意中间件函数返回的是gin.HandlerFunc,不是直接调用 - 若需部分接口跳过校验(如公开的 /health),应在中间件内根据
c.FullPath()或c.Request.URL.Path白名单放行,而不是拆成多个 router group - 别在中间件里做数据库查询验证用户状态(如是否被封禁),这属于业务逻辑,应放在 handler 开头,避免每次请求都查库
JWT 的 payload 是 Base64 编码、可解码的,永远别放密码、身份证号这类敏感信息;过期时间设太长会增加风险,太短又影响体验,常见折中是 24 小时 + 前端定时刷新机制——这点最容易被忽略,而且没法靠框架自动解决。


















