在 fiber 中间件里,先用 c.get("authorization") 获取头,strings.cut 安全提取 bearer token;校验非空、长度、点号后,用 github.com/golang-jwt/jwt/v5 解析验证,将 *myuserclaims 存入 c.locals("user_claims") 供下游使用。

中间件里怎么获取并解析 Authorization 头里的 Bearer Token
Fiber 的中间件函数接收 fiber.Ctx,令牌通常藏在 Authorization 请求头里,格式为 Bearer xxx。直接调用 c.Get("Authorization") 拿到字符串后,得手动切分——别用 strings.Split 粗暴分割,空格可能有多个,推荐用 strings.Cut 或正则匹配更稳妥。
拿到 token 字符串后别急着验签,先检查是否为空、长度是否合理(JWT 一般 > 100 字符)、是否含点号(.)——这些能快速拦截明显非法输入,避免后续解析失败抛 panic。
- 用
strings.TrimSpace清理头值前后空格 - 用
strings.HasPrefix(auth, "Bearer ")判断前缀,再取子串auth[7:],比Split更安全 - 若 token 缺失或格式不对,直接
c.Status(401).SendString("missing or malformed token"),不往下走
用 github.com/golang-jwt/jwt/v5 验证 JWT 并提取 claims
Fiber 本身不绑定 JWT 库,目前最稳的是 github.com/golang-jwt/jwt/v5(v4 已 deprecated)。验证核心是两步:解析 token 字符串 + 校验签名和有效期。注意 ParseWithClaims 第三个参数要传一个实现了 jwt.Claims 的结构体指针,不能传 jwt.MapClaims{} 这种字面量——会 panic。
OAuth2 常见 claims 如 sub(用户 ID)、scope(权限范围)、exp(过期时间)都得从 claims 里取。别在中间件里重复 new key,把密钥(如 RSA 公钥或 HMAC secret)提成全局变量或从配置加载一次即可。
- 用
jwt.Parse[YourCustomClaims](tokenStr, func(t *jwt.Token) (interface{}, error) { return yourPublicKey, nil }) - 检查
err != nil和!token.Valid两种失败情况,前者是解析失败(如签名错),后者是 claims 校验失败(如过期) - 验签通过后,把
claims.Sub或claims["scope"]转成字符串切片塞进c.Locals,供下游 handler 用
如何把校验结果透传给业务 handler 且避免重复解析
Token 解析和验证是 CPU 密集操作,千万别在每个 handler 里重做一遍。Fiber 的 c.Locals 是中间件间传递数据的正确方式——它只在当前请求生命周期内有效,线程安全,且比用 c.Context().Value 更符合 Fiber 习惯。
但要注意:c.Locals 存的是 interface{},取的时候必须类型断言。如果下游 handler 期望拿到 *MyUserClaims,中间件里就得存指针,而不是 struct 值;否则断言会失败。
- 中间件中:
c.Locals("user_claims", claims)(claims 是 *MyUserClaims) - handler 中:
if claims, ok := c.Locals("user_claims").(*MyUserClaims); ok { ... } - 如果只是简单透传 user ID,存
c.Locals("user_id", claims.Subject)更轻量,避免下游还要断言
Scope 权限校验该放在中间件还是 handler 里
粗粒度权限(如“必须登录”)适合放认证中间件,细粒度(如“必须有 read:orders”)建议挪到路由级中间件或 handler 开头。硬塞进统一鉴权中间件会导致所有接口被 scope 绑死,灵活性差。
一个实用做法是:认证中间件只负责解析 token、校验签名和基础 claims(iss、exp、aud),把 scope 字段解析成字符串切片存到 c.Locals("scopes");再写一个独立的 RequireScope("read:orders") 中间件,按需挂载到特定路由。
-
RequireScope中用strings.FieldsFunc(scopesStr, func(r rune) bool { return r == ' ' || r == ',' })拆 scope 字符串 - 用
map[string]struct{}预建 scopes 集合,比每次slice.Contains查更快 - 如果 scope 不满足,返回
403 Forbidden,不是401——状态码语义要准











