Beego中间件必须用beego.InsertFilter()注册且须在beego.Run()前调用,不支持Use()或UseMiddleware();不同阶段(BeforeRouter/BeforeStatic/BeforeExec)触发时机不同,静态资源仅能通过BeforeStatic拦截,RBAC校验应放在BeforeExec并配合路径标准化与ctx.StopRun()终止。

Beego中间件必须用InsertFilter注册,不是Use或UseMiddleware
Beego没有Use()或UseMiddleware()这类方法。所有中间件逻辑都得靠beego.InsertFilter()注册,且必须在beego.Run()之前调用——通常放在main()开头或init()里。注册晚了就完全不生效。
常见错误是把InsertFilter()写在某个Controller里,或者放在Run()之后,结果请求压根不进你的函数。
多个过滤器按注册顺序执行,但阶段不同,触发时机天差地别:
-
beego.BeforeRouter:所有非静态请求都走这里,但/static/路径完全绕过它 -
beego.BeforeStatic:唯一能拦截/static/users/123/private/file.pdf这种路径的阶段 -
beego.BeforeExec:路由已匹配成功、Controller还没执行,这时ctx.Input.Param(":id")才可用
RBAC校验必须放在BeforeExec阶段,不能放Prepare里
Prepare()方法看似方便,但它在Controller实例化后才调,此时HTTP body可能已被读取一次(比如JSON解析),且无法可靠拿到完整路由参数(如:id)。RBAC需要精确映射POST /api/v1/orders/:id → order:update,而Prepare()做不到这点。
正确做法是用beego.BeforeExec阶段注册过滤器:
- 先调
strings.TrimSuffix(ctx.Request.URL.Path, "/")标准化路径,避免/user和/user/被当成两个资源 - 把
ctx.Request.Method + " " + path拼成resource:action格式,例如GET /api/v1/users → user:list - 权限表只存
user:delete这类code字段,不硬编码URL或menu_id - 校验失败必须调
ctx.StopRun()立即终止,否则后续Controller仍会执行
静态资源权限要单独处理,BeforeStatic阶段不能用ctx.Controller
Beego对/static/下文件有独立处理流程,BeforeRouter和BeforeExec都无效。想控制/static/users/:userId/private/*,必须用beego.BeforeStatic。
但这个阶段陷阱极多:
-
ctx.Controller和ctx.Input.Param(":userId")尚未初始化,直接访问会panic - 得手动从
ctx.Request.URL.Path里用正则提取:userId,例如^/static/users/([0-9]+)/private/ - 路由需配合正则定义:
beego.Router("/static/users/:userId([0-9]+)/private/*", &controllers.StaticAuthController{})(注意这不是中间件注册,而是为触发BeforeStatic做铺垫) - 校验逻辑要查会话或token,确认当前用户是否有权访问该
userId对应的私有目录
Casbin集成比手写RBAC更稳,但要注意策略加载时机
自己实现RBAC容易漏边界:路径标准化、HTTP方法参与判定、角色继承、缓存一致性……用casbin能省掉大量胶水代码。它把策略加载进内存map,后续检查全是O(1)查找。
关键细节:
- 模型文件
rbac_model.conf必须包含[role_definition] g = _, _,否则角色继承失效 - 初始化Casbin时,用
casbin.NewEnforcer("rbac_model.conf", adapter),adapter建议选beego-orm-adapter适配Beego ORM - 不要在每次请求里重复NewEnforcer——它本身是线程安全的,全局复用一个实例即可
- 权限检查用
e.Enforce(uid, resource, action),返回true/false,失败直接ctx.StopRun()
最易忽略的是:Casbin策略里的resource字段,如果对应的是树状目录(比如只存目录ID而非完整路径),那校验前得先根据请求路径反查出所属目录ID,再传给Enforce()——这步漏了,权限就形同虚设。











