JWT签名验证总失败的主因是密钥类型不匹配(如HS256用[]byte、RS256用*rsa.PublicKey)以及时钟偏差未配置容忍(需显式设置WithTimeFunc),而Valid()仅校验exp/nbf,iss/aud须手动验证。

JWT签名验证为什么总失败?
Go微服务里用jwt-go做认证,最常卡在签名验证失败——不是密钥没对上,就是时钟偏差没处理。默认VerifyExpiresAt校验会严格比对exp字段,而服务间时间不同步几秒就直接报token is expired。
- 必须显式设置
WithTimeFunc容忍时钟偏差,比如time.Now().Add(5 * time.Second) -
ParseWithClaims里传入的密钥类型要和签发时一致:HS256用[]byte,RS256用*rsa.PublicKey - 别信文档里“自动校验”的说法,
Valid()只检查exp/nbf,不校验iss或aud,得手动加VerifyIssuer和VerifyAudience
如何让Gin中间件只拦截API路径?
把认证中间件挂到gin.Engine全局,结果静态资源、健康检查接口全被拦住——这不是设计缺陷,是路径匹配逻辑没写对。
- 用
engine.Group("/api")单独划分API路由,中间件只注册到该Group - 如果必须全局拦截,改用
engine.Use(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/api/") { /* 验证 */ } else { c.Next() } }) - 注意
c.Request.URL.Path不含查询参数,但c.FullPath()可能含通配符,优先用前者做前缀判断
RBAC权限检查怎么避免每次查DB?
用户登录后每次请求都查一次user_role_permissions表,QPS上不去还拖慢响应——权限数据变动频率低,完全可缓存。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用
sync.Map存map[userID][]string(权限码列表),首次加载后定时刷新,不用引入Redis增加链路复杂度 - 权限检查函数接收
context.Context和requiredPermission string,直接查map是否包含该字符串,strings.Contains比循环快 - 注意
sync.Map的Load返回interface{},记得类型断言:if perms, ok := cache.Load(userID); ok { if _, has := perms.([]string)[requiredPerm]; has { ... } }
Refresh Token该不该存数据库?
用Redis存refresh token看似合理,但实际容易踩两个坑:过期时间设错导致token永不过期,或删库时漏掉关联记录。
立即学习“go语言免费学习笔记(深入)”;
- Refresh token必须绑定设备指纹(如User-Agent+IP哈希),否则同一账号多端登录会互相踢下线
- 存Redis用
SET key value EX 7200,且key带用户ID前缀:refresh:12345:abcde,方便按用户批量清理 - 签发新access token时,旧refresh token要立即
DEL,不能等过期——否则重放攻击可重复使用
权限模型越细,token payload越大;payload太大又影响HTTP头传输效率。真要支持动态权限变更,别硬塞进JWT,用独立的权限服务+短时效token更稳。

















