Go Web校验JWT需同时解析签名并验证exp/iat等声明,须用jwt.ParseWithClaims传入具体claims结构体和匹配密钥的回调函数,中间件要规范提取Bearer Token并透传claims。

Go Web接口校验JWT合法性,核心就两件事:解析签名 + 验证声明(尤其是 exp 和 iat),缺一不可。只验签名不查过期,等于给过期令牌开绿灯;只查时间不验签名,攻击者随便改个 user_id 就能越权。
用 jwt.ParseWithClaims 解析并校验签名和标准声明
这是最常出错的一步——很多人直接用 jwt.Parse,它默认不校验 exp/iat,导致过期 token 仍被接受。
- 必须传入具体 claims 结构体(如自定义
CustomClaims或jwt.MapClaims),不能只传空指针 - 密钥回调函数必须返回
[]byte,且类型要匹配签名算法(如SigningMethodHS256对应 HMAC) - 错误类型要细分处理:
*jwt.ValidationError的Errors字段可位与判断是过期、未生效还是签名错误
func ParseToken(tokenStr string) (*CustomClaims, error) {
claims := &CustomClaims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return jwtKey, nil
})
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
switch {
case ve.Errors&jwt.ValidationErrorExpired != 0:
return nil, errors.New("token expired")
case ve.Errors&jwt.ValidationErrorNotValidYet != 0:
return nil, errors.New("token not active yet")
default:
return nil, errors.New("invalid token signature")
}
}
return nil, err
}
if !token.Valid {
return nil, errors.New("token invalid")
}
return claims, nil
}
中间件中提取 Authorization: Bearer xxx 并透传用户信息
常见坑是没统一处理 header 格式,或忽略大小写、空格、前缀缺失等边界情况。
-
r.Header.Get("Authorization")返回值可能带空格,要用strings.TrimSpace - 前缀判断必须用
strings.HasPrefix(auth, "Bearer "),不能硬切[7:](否则遇到"bearer xxx"或"Bearerxxx"就 panic) - 校验通过后,用
context.WithValue存入claims,但注意:value 类型要一致,建议封装成导出常量 key,避免字符串拼错
自定义 claims 结构体必须嵌入 jwt.RegisteredClaims(v5 推荐)
v4 中用 jwt.StandardClaims,v5 已弃用,改用 jwt.RegisteredClaims。不嵌入会导致 exp/iat 等字段无法被自动校验。
立即学习“go语言免费学习笔记(深入)”;
- 结构体字段名必须小写 +
jsontag,否则MapClaims转换会丢数据 - 自定义字段(如
UserID)要加jsontag,否则解析后为空 - 别在 claims 里存敏感信息(如密码、手机号),JWT 是 Base64Url 编码,不是加密
type CustomClaims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
真正难的不是写对这几行代码,而是上线后面对并发刷新 token、时钟不同步、密钥轮换这些场景——比如 exp 时间戳依赖服务器本地时间,若 NTP 同步失败,整个鉴权链就不可信。这些细节,往往比语法更决定系统是否真的安全。










