JWT 认证中间件设计

Access/Refresh 双令牌、签名校验、中间件注入上下文与吊销策略;和 go-auth-and-jwt 概念篇配合的工程设计笔记。

#type / concept #status / growing #tech / dev / backend #resource / go

[!info] 关联笔记

JWT 认证中间件设计

这个概念为什么出现

仅“会用 jwt 库签名”不够。服务要回答:

  • Access 短命如何刷新?
  • 中间件如何把身份注入后续 handler?
  • 注销如何尽快失效?
  • 算法与密钥如何防踩坑?

本篇聚焦中间件与双令牌流程;基础 JWT 结构见 go-auth-and-jwt

[!abstract] 一句话理解 校验 Bearer access token → 解析 Claims → 写入 context;refresh 换发新 access;吊销靠 TTL 黑名单或版本号。

双 Token 策略

TokenTTL 经验存放用途
Access短(分钟~小时级,按威胁模型)内存/严格存储API 鉴权
RefreshHttpOnly Cookie 或安全存储 + 服务端可吊销记录换 access

表中“7 天 access”等具体数字仅作旧笔记示例;生产应按风险缩短 access TTL

Claims 设计

type Claims struct {
	UserID string `json:"uid"`
	jwt.RegisteredClaims
}
  • exp/iat/nbf 用 RegisteredClaims
  • jti(JWT ID)便于黑名单
  • 不要塞权限全集过大或敏感 PII

库常用 golang-jwt/jwt(社区)。

中间件骨架(net/http)

type ctxKey int
const userIDKey ctxKey = 1

func UserIDFrom(ctx context.Context) (string, bool) {
	v, ok := ctx.Value(userIDKey).(string)
	return v, ok
}

func AuthMiddleware(secret []byte) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			h := r.Header.Get("Authorization")
			if !strings.HasPrefix(h, "Bearer ") {
				http.Error(w, "unauthorized", http.StatusUnauthorized)
				return
			}
			raw := strings.TrimPrefix(h, "Bearer ")
			claims, err := parseAndValidate(raw, secret)
			if err != nil {
				http.Error(w, "unauthorized", http.StatusUnauthorized)
				return
			}
			// 可选:Redis 黑名单 Exists(jti)
			ctx := context.WithValue(r.Context(), userIDKey, claims.UserID)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

校验要点

  1. 强制算法jwt.WithValidMethods([]string{"HS256"}) 或非对称套件,防 alg=none / 算法混淆
  2. 密钥:HS* 用足够长随机;RS*/ES* 管私钥
  3. 时钟偏移:少量 Leeway
  4. HTTPS 传输

刷新流程

客户端过期 → POST /auth/refresh + refresh token
服务端验 refresh(签名+存储会话/轮换)
→ 签发新 access(可选轮换 refresh)

Refresh 轮换:每次使用作废旧 refresh,防重放。

吊销

方案说明
短 access + 等过期简单,窗口内仍有效
jti 黑名单(Redis TTL)注销立即生效
token 版本号存用户表改密后全局失效

与分层

  • 中间件:认证(你是谁)
  • Service:鉴权(你能否做)
  • 不要在 JWT 里只信客户端角色字段而不校验服务端权威数据(敏感权限)

边界

  1. JWT 不是加密,是签名;载荷可见。
  2. 吊销与无状态之间的张力——完全无状态难以立即登出。
  3. 移动端与 Web 存储策略不同(XSS/CSRF)。

常见误区

[!warning] 从 token 解析后不校验签名
等同明文。

[!warning] 密钥写死仓库
用配置/密钥管理。

[!warning] access TTL 过长又无吊销
泄露窗口过大。

工程实践

  1. 统一 401 与 WWW-Authenticate 策略
  2. 审计登录/刷新/注销
  3. 限流登录与刷新接口
  4. 表驱动测:过期、错误签名、错误 alg、黑名单命中
  5. Gin 版本只是把 c.Request.Context() 换适配,核心不变

自测题

  1. 为何 access 要短、refresh 要可吊销?
  2. 中间件应把什么放入 context?
  3. alg 混淆攻击防什么?
答案
  1. 降低泄露窗口;长会话可服务端作废。
  2. 已验证的稳定身份标识(user id),而非整段原始 token(除非审计需要)。
  3. 攻击者改 alg 用公钥当 HMAC 密钥等;强制预期算法。

延伸阅读


笔记元信息

  • 文件:jwt-auth-middleware-go.md
  • 状态:已深化
创建于 2026/6/25 更新于 2026/7/15