鉴权中间件必须在认证中间件之后注册,且需预热加载权限映射、使用自定义context key类型、标准化路径匹配、严格校验JWT标准声明、区分用户与服务间JWT、确保mTLS或gRPC元数据安全。

Go 框架鉴权不是加个中间件就完事,顺序错、key 用字符串、路径匹配硬编码、每次查 DB——这些都会让服务在线上 panic 或被绕过。
鉴权中间件必须在认证中间件之后注册
认证中间件(如 JWT 解析)负责把 user_id、role 写进 context.Context,权限中间件只读不写。顺序颠倒会导致 ctx.Value("user_id") 是 nil,后续所有校验直接 panic。
- Gin 中必须写成
router.Use(authMiddleware, permMiddleware),不能反过来 - chi 中要用
r.Use(AuthMiddleware).Use(PermMiddleware),链式调用顺序不可交换 - 如果用了自定义 context key,记得提前定义类型:
type userKey string; const userIDKey userKey = "user_id",别用"user_id"字符串字面量当 key
路径匹配别用 r.URL.Path 做字符串判断
r.URL.Path 是原始路径,没标准化,比如 /users/123/、/users/123?foo=bar 都能绕过 strings.Contains(r.URL.Path, "/admin") 这类判断。
- Gin 推荐用
c.FullPath()获取路由模板(如/users/:id),再正则替换/:([^/]+)→/{\1},得到标准键/users/{id} - 预热加载时就把
"GET:/users/{id}"映射到权限集,运行时只查 map,避免每次请求都查 DB - 显式放行公共路径:
if c.FullPath() == "/health" || c.Request.Method == "OPTIONS" { c.Next(); return }
JWT 校验必须显式检查标准声明字段
只验签名不校验 exp、iat、iss,等于给重放、过期、冒用留后门。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 用
github.com/golang-jwt/jwt/v5,别用已废弃的jwt-go或 v4(有 CVE-2023-37582 等漏洞) - claims 结构体要嵌入
jwt.RegisteredClaims,然后调用claims.Validate()一键校验 - keyFunc 回调里加算法白名单:
if _, ok := token.Header["alg"]; !ok || token.Header["alg"] != "HS256" { return nil, errors.New("invalid alg") } - 密钥必须是 ≥32 字节的
[]byte,从环境变量读,别硬编码"secret"
微服务间鉴权不能复用用户 JWT 中间件
服务间调用和用户请求的 JWT 来源、有效期、签发者、aud 都不同,混用等于裸奔。
- 服务间 JWT 必须用 RS256 签名,公钥启动时加载,私钥只存于统一授权中心
- HTTP 中间件得先确认
req.TLS != nil && len(req.TLS.PeerCertificates) > 0,确保 mTLS 已建立 - 校验时严格比对
claims.Audience是否等于当前服务名(如"user-service") - gRPC 场景下从
metadata.FromIncomingContext(ctx)提取authorization键,不要复用 HTTP header 解析逻辑
最常被忽略的其实是预热加载和 key 类型——权限映射不进内存,QPS 上千时 DB 直接拖垮;context key 用字符串,第三方包一覆盖就丢数据。这两点不处理,其他都白搭。

















