受保护路由必须用 group() 隔离,jwt 中间件仅限 /api 等认证前缀;必须使用 echo-jwt/v4,显式校验 token.valid;密钥为 []byte 类型,refresh 接口须事务原子执行。

受保护路由必须用 Group() 隔离,不能全局 Use()
直接在 e.Use() 里加 JWT 中间件,会导致所有路由(包括 /login)都被拦截,结果登录接口永远返回 401。正确做法是只对需要认证的路径前缀做分组,比如:
-
auth := e.Group("/api")—— 所有/api/xxx路由才走认证 -
public := e.Group("/v1")——/v1/health这类接口天然跳过中间件 - 绝对不要写
e.Use(middleware.JWT(...)),除非你确认每个接口(含健康检查、文档页、登录)都该被拦
JWT 中间件必须用 echo-jwt/v4,且要显式校验 token.Valid
官方已移除内置 JWT 支持,github.com/labstack/echo-jwt/v4 是唯一推荐包。常见错误是只判 err != nil 就放行,但 token 过期时 err 为 nil、token.Valid 为 false:
- 配置里必须设
Config.SigningKey = []byte("your-secret"),类型必须是[]byte,不是string - 若用 HS256,密钥长度建议 ≥32 字节;RSA 则需预加载公钥,别每次从文件读
- 中间件默认只从
Authorization: Bearer xxx提取,若想支持query:token,得手动配Config.TokenLookup = "query:token"
用户信息必须通过 c.Set() 注入,且 handler 中要用 c.Get() 安全取出
中间件校验成功后,echo-jwt 会把解析出的 claims 存进 c.Get("user"),但前提是:你没在中间件外覆盖或误删这个 key。
- 别在 handler 里写
ctx.Value("user")—— Echo 的上下文封装和标准库不同,c.Get()才是安全访问方式 - 如果自定义了 claims 结构体(比如带
UserID uint字段),记得在中间件配置里传Config.Claims = &MyClaims{} - 登录接口本身不能被 JWT 中间件包裹,否则拿不到原始账号密码;签发 token 后直接
c.JSON(200, map[string]string{"token": signedStr})
/auth/refresh 接口不能拆成多个中间件,必须原子执行
双 Token 场景下,/auth/refresh 是最易出漏洞的环节。中间件链式执行会让“查旧 token → 删旧 token → 发新 token”三步被并发请求打断。
- 整个刷新逻辑必须收在一个 handler 函数里,用数据库事务包裹:
tx, _ := db.BeginTx() - 不能依赖中间件解析出的
c.Get("user_id")去查库——攻击者可能复用已作废的 refresh_token,必须重新查fingerprint和expires_at - 签发新 access_token 时,
exp必须用time.Now().Add(30*time.Minute).Unix(),别手算秒数,也别复用旧 token 的 exp











