权限中间件必须显式注册到路由组而非全局,否则会误拦健康检查等请求;正确做法是用Party分组后调用Use()绑定角色白名单,并确保OPTIONS预检路由在权限中间件前注册。

权限中间件必须显式注册到路由组,不能只挂全局
权限控制不是“写个函数往 app.Use() 一塞”就完事的。Iris 的 app.Use() 只影响后续注册的路由,而权限逻辑往往需要和具体路径、HTTP 方法强绑定——比如 /admin/users 只允许 admin 角色,/api/orders 允许 user 和 admin。如果把权限中间件全局注册,它会拦截所有请求(包括 /health、/favicon.ico),反而导致健康检查失败或静态资源异常。
正确做法是用 Party 分组后注册:
admin := app.Party("/admin")
admin.Use(AuthMiddleware("admin")) // 只对 /admin 下所有路由生效
admin.Get("/users", usersHandler)
api := app.Party("/api")
api.Use(AuthMiddleware("user", "admin"))
api.Get("/orders", ordersHandler)
- 权限中间件函数应返回
iris.Handler,内部通过ctx.Values().Get("user")拿用户角色 - 若角色不匹配,必须调用
ctx.StatusCode(403)+ctx.StopExecution(),否则请求仍会进入 handler - 不要在中间件里直接
panic或log.Fatal,这会让整个服务崩溃
ctx.User.Set() 不等于权限校验,只是数据传递载体
ctx.User.Set() 是 Iris 提供的用户上下文绑定机制,但它本身不做任何鉴权判断。很多人误以为调用了 ctx.User.Set(&user) 就自动拥有了权限,其实这只是把结构体存进当前请求生命周期的 context 值空间,后续中间件或 handler 还得自己取出来比对角色字段。
典型错误写法:
// ❌ 错误:只设值,没校验
func JWTAuth(ctx iris.Context) {
token := ctx.GetHeader("Authorization")
user, _ := parseToken(token)
ctx.User.Set(user) // 这步只是存,不拦请求
ctx.Next() // 请求照常往下走
}
正确模式是:解析 → 校验 → 拒绝或放行:
- 从
ctx.Request().Header.Get("Authorization")提取 Bearer Token - 用
jwt.ParseWithClaims验签并检查ExpiresAt - 查出用户角色后,用
roles := user.Roles和白名单比对 - 不匹配时:
ctx.StatusCode(403)+ctx.WriteString("forbidden")+ctx.StopExecution()
iris-admin 的 permRoutes 才是企业级权限的落地点
纯手写中间件能解决基础角色控制,但一旦涉及菜单动态渲染、按钮级权限、审计日志、变更追踪,就必须接入 iris-admin 的 permRoutes。它不是装饰器,而是一套元数据注册系统:每条路由都带 Method、Path、Roles、Middleware 字段,删除时自动触发日志输出 "iris-admin: delete X router"。
绕过它直接操作 Party.Reset() 会导致权限脱节:
- 前端菜单还显示“删除订单”按钮,但点击后后端返回 403(因为路由元数据没同步)
- 管理员删了某条路由,但日志里查不到记录,审计无法回溯
- 新注册的路由没进
permRoutes,RBAC 策略压根不生效
所以初始化阶段要显式调用:
admin.RegisterPermRoute(iris.Get, "/admin/logs", []string{"admin"}, nil)
权限中间件里别漏掉 OPTIONS 预检请求的放行
带认证头(如 Authorization: Bearer xxx)或自定义 header(如 X-Request-ID)的跨域请求,浏览器会先发 OPTIONS 预检。如果权限中间件没处理这个方法,预检响应里缺 Access-Control-Allow-Headers 或直接 403,前端就卡死在 preflight 阶段,报错 Response to preflight request doesn't pass access control check。
解决方案不是让权限中间件“支持 OPTIONS”,而是提前在对应路由组注册通配 OPTIONS 处理:
- 在
admin := app.Party("/admin")后立刻加:admin.Options("*", func(ctx iris.Context) { ctx.StatusCode(204) }) - 确保该
OPTIONS路由注册在权限中间件.Use()之前,否则中间件根本不会执行 - 不要在权限中间件里写
if ctx.Method() == "OPTIONS" { ctx.StatusCode(204); return }—— 路由未匹配,中间件压根不触发
权限逻辑本身不该管预检,那是 CORS 层的事;但它的注册时机和路由覆盖范围,直接影响预检能否成功。


















