鉴权中间件必须在 MVC Party 注册前挂载:先获取 Party 实例并调用 Use(),再传给 mvc.New();JWT 失败时须调用 ctx.StopExecution() 终止流程;控制器需手动从 ctx.Values() 获取用户信息;角色权限应统一收口至中间件。

鉴权中间件必须在 MVC Party 注册前挂载
Iris 的 MVC 应用本质是绑定在一个 app.Party 路由组上的,而中间件的执行顺序取决于它被注册的位置。如果你把鉴权中间件写在 mvc.New(app.Party("/api")) 之后再调用 Use(),它不会生效——因为 MVC 的路由注册发生在 Handle() 阶段,此时中间件链已冻结。
正确做法是:先拿到 Party 实例,立刻挂载中间件,再传给 mvc.New():
api := app.Party("/api")
api.Use(authMiddleware) // ✅ 必须在这一步
mvcApp := mvc.New(api)
mvcApp.Handle(new(UserController))
- 如果漏掉这步,
authMiddleware根本不会进入请求链,控制器方法将裸奔执行 - 不要试图在
BeforeActivation里加中间件来“补救”,那只能影响单个 handler,无法覆盖整个 Party - 多个中间件按注册顺序执行,鉴权必须在日志、限流等之前,否则非法请求也可能被记录
JWT 解析失败时必须调用 ctx.StopExecution()
鉴权中间件里解析 token 失败后,常见错误是只写响应却不中断流程,比如:
if err != nil {
ctx.StatusCode(401)
ctx.JSON(BizError{Code: 40101, Msg: "token 无效"})
// ❌ 缺少 ctx.StopExecution() → 后续中间件和 controller 仍会执行
}
ctx.Next()
这会导致两个严重问题:一是业务逻辑被意外触发(比如删用户),二是日志或监控误判为“成功请求”。
- 正确写法是:
ctx.StatusCode(401); ctx.JSON(...); ctx.StopExecution(),且确保ctx.Next()在 if 分支外不被执行 -
ctx.StopExecution()和ctx.StopWithStatus()不同:前者彻底终止中间件链,后者只终止当前 handler 执行但不阻断中间件流转 - 测试时可故意传错 token,抓包确认响应体和状态码是否符合预期,且无多余日志输出
控制器内如何安全复用鉴权上下文
MVC 控制器方法签名中可以直接接收 iris.Context,但要注意:鉴权中间件注入的值(如用户 ID、角色)必须通过 ctx.Values().Get() 显式获取,Iris 不会自动绑定到结构体字段。
例如,在中间件中存入用户信息:
ctx.Values().Set("user_id", userID)
ctx.Values().Set("role", "admin")
在控制器中使用:
func (c *UserController) GetProfile(ctx iris.Context) {
userID := ctx.Values().GetInt64("user_id")
if userID == 0 {
ctx.StatusCode(401)
ctx.JSON(BizError{Code: 40102, Msg: "未登录"})
ctx.StopExecution()
return
}
// 继续业务逻辑
}
- 不要依赖
ctx.Params()或ctx.URLParam()获取鉴权数据,它们只处理路径和 query 参数 -
ctx.Values()是 request-scoped 的,每个请求独立,无需担心并发污染 - 如果控制器结构体里定义了
UserID int64字段,它不会被自动赋值——Iris MVC 不做隐式注入,必须手动取
Role-based 权限控制别硬编码在每个方法里
把 if role != "admin" 写满每个 controller 方法,既难维护又易遗漏。推荐统一收口到一个中间件或封装工具函数:
func requireRole(allowed ...string) iris.Handler {
return func(ctx iris.Context) {
role := ctx.Values().GetString("role")
for _, r := range allowed {
if r == role {
ctx.Next()
return
}
}
ctx.StatusCode(403)
ctx.JSON(BizError{Code: 40301, Msg: "权限不足"})
ctx.StopExecution()
}
}
然后在路由分组上组合使用:
adminAPI := api.Party("/admin")
adminAPI.Use(requireRole("admin", "super"))
mvc.New(adminAPI).Handle(new(AdminController))
- 避免在 controller 方法内部做角色判断,那会破坏单一职责,也绕过中间件的可观测性
- RBAC 规则变更时,只需改
requireRole调用处,不用翻遍所有 controller 文件 - 如果某些接口需要动态权限(如“只能删自己的文章”),仍需在 controller 内校验,但基础角色门槛应前置
StopExecution 漏掉一次,就可能让非法请求穿透到业务层。


















