主流且安全的 jwt 选型是 github.com/golang-jwt/jwt/v5,它修复了 v4 的签名绕过漏洞(如 cve-2023-37582),api 更清晰,必须升级;需显式指定算法、校验标准声明、使用双 token 模式等。

用 github.com/golang-jwt/jwt/v5 生成 token,别再用 v4 或 jwt-go
现在主流且安全的选型是 v5,它修复了 v4 中多个已知的签名绕过漏洞(如 CVE-2023-37582),并且 API 更清晰。如果你还在用 github.com/dgrijalva/jwt-go 或 v4,升级不是“可选”,而是必须。
常见错误现象:
- 调用
Parse时返回token is invalid,但实际 token 格式完全正确 —— 很可能是算法未显式校验导致的静默失败 - 使用
SigningMethodNone被意外接受,v5默认拒绝该算法,而旧版需手动拦截
实操建议:
- 初始化 token 时明确传入
jwt.SigningMethodHS256,不要依赖默认或反射推断 - 密钥必须是
[]byte,且长度不低于 32 字节(HS256 要求);硬编码"secret"会直接导致线上被爆破 -
v5移除了ParseWithClaims的泛型简化版,必须显式传入指针类型,例如:jwt.Parse[MyClaims](tokenStr, keyFunc)
Parse 验证 token 时,必须校验 exp、iat 和 iss,不能只看签名
仅验证签名只防篡改,不防重放、不防过期、不防冒用。很多线上事故源于解析后没检查标准声明字段。
典型漏检场景:
- 用户登出后 token 仍能访问接口(没校验
exp) - 攻击者截获旧 token,在服务重启后继续使用(没校验
iat是否早于服务启动时间) - 第三方伪造 issuer(比如把
iss: "my-api"改成iss: "attacker.com")
实操建议:
- 在
keyFunc回调里,顺便检查token.Header["alg"]是否为预期算法,拒绝none或RS256(除非你真用了 RSA) - 用
WithValidMethods([]string{"HS256"})显式限定允许的签名方法 - 在 claims 结构体中嵌入
jwt.RegisteredClaims,它自带Validate方法,可一键校验exp/nbf/iat/iss等
Gin 中间件里提取并验证 token,Authorization: Bearer xxx 是事实标准,别自定义 header
客户端传 token 的方式五花八门:query 参数、cookie、X-Token header……但只有 Authorization: Bearer <token></token> 是 RFC 6750 明确规定的 bearer token 传输方式,也是所有网关、WAF、API 网关(如 Kong、Apigee)默认识别的格式。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
容易踩的坑:
- 从 cookie 读 token → 增加 XSS 风险,且无法跨域携带(尤其 WebSocket 场景)
- 用
X-Auth-Token→ OpenAPI 文档工具(如 Swagger UI)不自动注入,前端调试困难 - 没 trim header 值 → 客户端多加了个空格导致
Parse失败,报错却是token contains an invalid number of segments
实操建议:
- 中间件里统一用
c.Request.Header.Get("Authorization"),然后按空格分割,取第二段 - 加一行
tokenStr = strings.TrimSpace(tokenStr),避免不可见字符干扰 - 验证失败时,返回
401 Unauthorized,**不要**返回403 Forbidden—— 后者暗示身份已确认但权限不足
刷新 token 不等于延长原 token 过期时间,要用双 token(access + refresh)模式
很多人以为“刷新”就是把原 token 的 exp 改大再签一次,这是错的。这样既无法主动让旧 access token 失效,又违背 JWT 无状态设计初衷。
真正可行的方案是分离职责:
-
access_token:短时效(如 15 分钟),用于日常接口调用,不存库,过期即弃 -
refresh_token:长时效(如 7 天),带唯一 ID 和绑定设备指纹,存数据库,每次使用后立即失效并生成新对
关键细节:
-
refresh_token必须 HttpOnly + Secure Cookie 传输,禁止 JS 访问 - 刷新接口必须校验
refresh_token的 DB 存在性、未被吊销、且与当前请求的User-Agent/IP匹配度在合理范围内 - 不要在 access token 里塞 refresh token 的 hash —— 这会让 access token 变得臃肿,且泄露后直接废掉整个刷新链
复杂点在于:refresh token 的存储和吊销需要原子操作。如果用 Redis,推荐用 SET key value EX 604800 NX 写入,并用 Lua 脚本保证“读-校验-删-写”四步不被并发打乱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










