路由分组必须用e.Group()显式声明,不可靠中间件动态切换;需为v1、v2等版本分别创建独立Group并绑定专属JWT中间件实例,实现密钥、TokenLookup、ErrorHandler及RBAC的物理隔离。

路由分组必须用 e.Group() 显式声明,不能靠中间件“动态切换”
很多人想在同一个中间件里根据路径前缀(比如 /api/v1 vs /api/v2)自动切 JWT 配置,这是错的。Echo 的中间件是静态绑定的,e.Use() 注册的是全局行为,没法在运行时按请求路径返回不同密钥或不同 TokenLookup。真正可行的方式是:为每个需要差异化 JWT 规则的路径段,单独创建 echo.Group,再各自挂专属中间件实例。
常见错误是写成这样:
e.Use(func(next echo.Handler) echo.Handler {
return func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/v1") {
// 尝试在这里初始化 v1 版 JWT 中间件 —— 不行,next 已固定
}
return next(c)
}
})
这只会让逻辑混乱、密钥复用出错、甚至 panic。正确做法是物理隔离:
-
v1Group := e.Group("/api/v1")→ 挂 HS256 + 静态密钥的echojwt.WithConfig -
v2Group := e.Group("/api/v2")→ 挂 RS256 +Keyfunc动态加载公钥的配置 - 两个 Group 之间完全不共享中间件实例,避免 ContextKey 冲突(比如都用
"user"但解析结构不同)
echojwt.Config 必须按组独立构造,签名密钥和错误处理不能复用
JWT 中间件不是单例,每次调用 echojwt.WithConfig() 都生成新实例。不同 Group 要用不同配置,否则 v2 接口可能被 v1 的密钥规则放行,或者错误响应格式不一致。
典型差异点:
- 密钥类型:
v1用[]byte("secret"),v2必须用Keyfunc返回*rsa.PublicKey,硬塞[]byte会 panic -
TokenLookup:v1 从header:Authorization读,v2 可能强制走cookie:access_token,必须显式设,不设就沿用默认值 -
ErrorHandler:v1 返回{"error":"invalid token"},v2 要求返回{"code":40101,"message":"token expired"},必须各自定义
示例片段(v2 组):
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
v2Group.Use(echojwt.WithConfig(echojwt.Config{
KeyFunc: func(token *jwt.Token) (interface{}, error) {
return getRSAPublicKey() // 动态加载
},
TokenLookup: "cookie:access_token",
ErrorHandler: func(c echo.Context, err error) error {
return c.JSON(http.StatusUnauthorized, map[string]interface{}{
"code": 40101,
"message": "token expired or invalid",
})
},
}))
权限中间件(如 RBAC)必须与 JWT 组严格对齐,别跨 Group 复用
RBAC 中间件依赖 c.Get("user"),而这个值由 JWT 中间件注入。如果 v1 和 v2 的 JWT 解析出的 user 结构不同(比如 v1 是 map[string]interface{},v2 是自定义 struct UserV2),却共用一个 RBAC 函数,运行时就会 panic 或权限误判。
安全做法是:
- v1Group.Use(jwtV1Middleware), v1Group.Use(rbacV1Middleware)
- v2Group.Use(jwtV2Middleware), v2Group.Use(rbacV2Middleware)
- rbacV1Middleware 里用
user, ok := c.Get("user").(map[string]interface{})断言 - rbacV2Middleware 里用
user, ok := c.Get("user").(*UserV2)断言
不要试图在 RBAC 里做类型泛化——类型不匹配就是设计缺陷,不是兼容性问题。
静态资源或公开接口千万别被 Group 级 JWT 拦截
给 e.Group("/api") 挂了 JWT,结果 /api/swagger.json 或 /api/healthz 全部 401,这是高频事故。Group 是前缀匹配,只要路径以该前缀开头就进链。
解决方式只有两种,没有第三种:
- 把公开路由拆出去,不进任何带 JWT 的 Group,比如
e.GET("/healthz", healthHandler)直接挂在根e - 用
e.Static("/swagger", "./docs")显式注册静态服务,它会绕过所有中间件(包括 Group 级)
别信“加个跳过逻辑在 JWT 中间件里”——那会让认证边界模糊,后期审计难追溯,且容易漏掉新接入的公开端点。


















