鉴权中间件必须显式处理options请求、jwt.parse解析失败需结合算法校验与密钥类型排查、防重放须独立于jwt状态外构建时间戳+nonce指纹机制。

直接用 jwt.Parse 做鉴权,90% 的线上问题出在 OPTIONS 请求被拦、token 解析静默失败、重放攻击没防住这三处——不是逻辑写错了,是默认行为和现实网络环境不匹配。
鉴权中间件必须显式处理 OPTIONS 请求
浏览器发 CORS 预检时只带 Origin 和 Access-Control-Request-Method,不带 Authorization。如果你的中间件一上来就校验 header,r.Method == "OPTIONS" 就会直接返回 401,真实请求永远到不了后端。
- 在中间件最开头加判断:
if r.Method == "OPTIONS" { w.WriteHeader(200); setCorsHeaders(w); return } -
setCorsHeaders至少包含:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers - 不要依赖
github.com/rs/cors这类库自动透传预检——它常把 CORS 和鉴权拆成两个中间件,顺序错位就漏掉 - 如果网关本身是唯一出口,CORS 头必须由它全量设置,后端服务可以完全不处理
jwt.Parse 解析失败时别信 err.Error() 的表面信息
square/go-jose: error in cryptographic primitive 这类错误几乎从不告诉你真实原因,实际可能是密钥类型错、padding 缺失、算法不匹配。
- 用
jwt.ParseWithClaims+ 显式jwt.WithValidMethods([]string{"HS256"}),避免协商时 fallback 到不支持的算法 - HS256 密钥必须是
[]byte;RSA 公钥必须是*rsa.PublicKey,混用会导致niltoken +err == nil - token 字符串末尾缺
=?先strings.TrimRight(token, "=")再补足,或用jwt.ParseUnverified看 payload 是否能 decode 出来 - 日志里打
token[:min(len(token), 20)],不然长 token 被截断,看不出前缀是否为eyJ
防重放不能靠 JWT payload 里的 nonce
JWT 是无状态的,但防重放必须有状态约束。把 nonce 塞进 token 里再全局去重,等于放弃无状态优势,还引入 Redis 清理、并发冲突、租户隔离等一堆新问题。
- 防重放逻辑独立于 token 解析:先校验
X-Timestamp(允许 ±30 秒偏差),再校验X-Nonce(服务端构造指纹存 Redis) - 指纹格式建议:
fmt.Sprintf("req:%s:%d:%s", userID, timestamp, nonce),超时设 60 秒 - 用
rdb.SetNX(ctx, key, "1", 60*time.Second),绝不用GET+SET,否则竞态下会放过重放 - 客户端必须用 NTP 同步时间,
X-Nonce必须是加密安全随机数(crypto/rand),不能拼在 URL 里
多租户场景下 Keyfunc 不要硬编码
不同业务线用不同密钥、不同 iss、甚至不同算法(HS256 / RS256 混用),硬编码一套 Keyfunc 会让上线变事故演练。
- 按路径前缀路由:
/api/v1/pay/→ 支付租户密钥,/api/v1/health/→ 医疗租户 RSA 公钥 -
Keyfunc返回值必须含密钥和预期claims.Issuer,并在ValidateClaims回调里显式校验claims.Issuer和claims.Audience - RSA 公钥从 PEM 加载:
pem.Decode→x509.ParsePKIXPublicKey,别直接读 raw bytes - 密钥从环境变量读 base64 字符串,启动时一次 decode,别在每次请求里重复解析
真正的难点不在写鉴权逻辑,而在让每个环节都对齐现实:浏览器怎么发预检、token 在 wire 上怎么传输、客户端时间是否可信、租户密钥如何热更新——这些细节漏掉一个,防护就形同虚设。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











