JWT核心结构为Header.Payload.Signature三部分,Header声明类型与算法,Payload含声明(仅Base64URL编码,非加密),Signature确保完整性;必须用jwt.ParseWithClaims校验,禁用ParseUnverified,密钥类型须匹配算法,exp校验需配leeway防时钟偏移,refresh token应服务端存储并一用即废。

Go 无状态认证的核心就是 JWT,但直接套用示例代码大概率会在生产环境出问题——密钥硬编码、exp 校验失败、中间件透传丢失上下文、Token 被重放,这些都不是“跑通就行”的小问题。
jwt.Parse() 必须配对使用 ParseWithClaims 和 SigningMethod.KeyFunc
常见错误是调用 jwt.ParseUnverified() 解析 payload 后手动判断 exp,这等于放弃签名验证。攻击者可任意修改 user_id 或 exp 后伪造 token。
正确做法是始终用 jwt.ParseWithClaims(),并确保 KeyFunc 返回的密钥类型与签名算法匹配:
- HS256:必须返回
[]byte,不能传字符串;若从环境变量读取,需用[]byte(os.Getenv("JWT_SECRET")) - RS256:
KeyFunc需返回*rsa.PublicKey,且私钥签名时要用对应*rsa.PrivateKey - 校验时若密钥类型错(比如 HS256 传了 rsa.PublicKey),
jwt.ParseWithClaims()会静默失败,token.Valid为 false,但err可能是 nil
AuthMiddleware 中必须显式注入 context 并校验 token 有效性
很多中间件只做解析,没检查 token.Claims.(jwt.MapClaims)["user_id"] 是否存在,或忽略 token.Header["alg"] 是否被篡改为 none(已知漏洞)。
关键逻辑不能省略:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 先用
strings.TrimPrefix(tokenStr, "Bearer ")剥离前缀,再strings.TrimSpace()清空两端空格 - 解析后必须同时判断
err == nil和token.Valid == true,二者缺一不可 - 将用户标识写入
c.Request.Context()(Gin)或r.Context()(net/http),而非c.Set()这类框架内部存储,避免 handler 中取不到 - 如果使用自定义
UserClaims结构体,务必实现Valid()方法校验exp、nbf等标准字段
refresh token 不该用 JWT 实现,而应走服务端存储 + 单次失效
把 refresh token 也做成 JWT 是典型误区。JWT 无法主动作废,一旦泄露就等于长期通行证。
生产环境应:
- refresh token 存 Redis,key 为
refresh:,value 存关联的 user_id 和过期时间 - 签发新 access token 时,生成唯一
jti,存其 SHA256 哈希到 Redis,并设置和 refresh token 相同的 TTL - 每次使用 refresh token,先查 Redis 是否存在且未被标记为已使用,成功后立即
DEL该 key(一用即废) - access token 的
exp控制在 15–60 分钟,refresh token 的 TTL 控制在 7 天以内
最易被忽略的是时钟偏移处理:服务端和客户端系统时间差超过 1 秒,exp 就会校验失败。不要调服务器时间,改用 jwt.WithLeeway(5 * time.Second) 传入 ParseWithClaims 的选项参数。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










