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 同步失败,整个鉴权链就不可信。这些细节,往往比语法更决定系统是否真的安全。