JWT签名验证必须用ParseWithClaims而非Parse,因Parse仅解码不校验签名;敏感字段应存Claims而非URL参数,payload无加密;需设exp/iat;刷新Token须用HttpOnly Cookie存储并每次失效;应使用github.com/golang-jwt/jwt/v5库。

JWT签名验证必须用ParseWithClaims而非Parse
直接调用Parse只会解码token结构,不校验签名,攻击者可篡改payload后重签(若你误用对称密钥且未设SigningMethod约束,风险更高)。必须显式指定jwt.MapClaims并传入密钥和验证逻辑:
token, err := jwt.ParseWithClaims(tokenString, &jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(secretKey), nil
})常见错误:漏掉token.Method类型检查,导致RSA公钥被误用于HMAC验证;或把secretKey写成硬编码字符串却没做环境隔离。
用户ID等敏感字段应存进Claims而非URL参数
JWT的payload是Base64Url编码、**无加密**的,任何中间人可解码查看。把user_id、role塞进自定义Claims没问题,但绝不能放密码哈希、手机号明文、邮箱全量字符串:
-
exp和iat必须设置,否则过期校验失效 - 自定义字段名避免和标准字段冲突(如别叫
iss、sub除非真按RFC 7519语义用) - 如果需要权限分级,用
scope数组比单个role字符串更易扩展
示例安全写法:
立即学习“go语言免费学习笔记(深入)”;
claims := jwt.MapClaims{
"user_id": 123,
"scope": []string{"read:order", "write:cart"},
"exp": time.Now().Add(24 * time.Hour).Unix(),
"iat": time.Now().Unix(),
}刷新Token需用RefreshToken双令牌机制
仅靠一个长期有效的Access Token存在泄露后无法主动作废的问题。正确做法是颁发短期Access Token(如15分钟)+ 长期Refresh Token(如7天),且Refresh Token必须:
- 存储在
HttpOnly+SecureCookie中,禁止JS访问 - 每次使用后立即失效(服务端记录已用过的Refresh Token hash,或绑定设备指纹)
- 与Access Token分离存储——不要把Refresh Token塞进JWT的
payload里
刷新接口典型流程:
func refreshHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("refresh_token")
if err != nil {
http.Error(w, "no refresh token", http.StatusUnauthorized)
return
}
// 1. 校验cookie中的refresh_token是否有效且未被使用
// 2. 生成新access_token(含新exp)
// 3. 在DB中标记该refresh_token为已使用
// 4. 返回新access_token(不返回新refresh_token,除非显式请求轮换)
}Go JWT库选github.com/golang-jwt/jwt/v5而非旧版
旧版github.com/dgrijalva/jwt-go已被弃用,存在关键漏洞(如CVE-2020-26160:None算法绕过)。v5版强制要求显式声明SigningMethod,默认禁用none算法,并修复了时钟偏移校验缺陷:
- 导入路径必须是
github.com/golang-jwt/jwt/v5,不是v4或无版本号 -
ParseWithClaims第二个参数必须是指针类型(&jwt.MapClaims{}),否则v5会panic - 校验时间时,v5默认允许
1s误差,若需更严格,用WithValidator自定义
最容易被忽略的是:v5的SigningMethodHS256.KeyFunc返回值必须是interface{},不是[]byte——传错类型会导致签名永远不匹配。










