beego 中 controller.needlogin 仅校验 session 存在,无法支持角色与权限控制;应通过中间件或 prepare() 结合角色配置、rbac 表设计及行级校验实现细粒度权限管理。

Beego 中用 Controller.NeedLogin 做基础权限拦截不靠谱
Beego 自带的 NeedLogin 只判断 Session 是否存在,完全不涉及角色和权限。直接依赖它,会导致所有登录用户都能访问 AdminController 里的接口,哪怕只是普通用户。真实场景里,你得区分 "admin"、"editor"、"viewer" 等角色,还要支持「某接口只允许 admin + editor 修改」这类组合逻辑。
实操建议:
- 别在
Prepare()里硬写if role != "admin" { this.Abort("403") }—— 角色字符串散落在各处,后期改一个权限就得 grep 全局 - 把角色和路由绑定关系抽成配置,比如用 map[string][]string 表达:
map["/api/users/delete"] = []string{"admin"} - 在自定义
Prepare()中统一查当前用户角色,再查路由所需角色集,用slice.Contains判断是否授权(注意:Beego 2.x 的utils.SliceContains可用,1.x 需自己写)
用 beego.Router 注册时传入中间件做权限校验
Beego 1.12+ 支持在 Router 第四个参数传 func(http.Handler) http.Handler 类型中间件,这是插入权限逻辑最干净的位置 —— 比在每个 Controller 里重复写 Prepare 更可控,也避免绕过 Controller 的直连 Handler 被跳过。
实操示例:
// router.go
beego.Router("/api/posts", &controllers.PostController{}, "get:List;post:Create")
beego.Router("/api/posts/:id:int", &controllers.PostController{}, "get:View;put:Update;delete:Delete")
<p>// 权限中间件:只允许 editor 及以上访问 /api/posts/<em>
authMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r </em>http.Request) {
userRole := GetRoleFromSession(r)
if !allowed(userRole, "editor", "admin") {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
beego.InsertFilter("/api/posts/*", beego.BeforeRouter, authMiddleware)
</p>
注意点:
-
InsertFilter的路径匹配是前缀式,/api/posts/*会匹配/api/posts/123/delete,但不会匹配/api/posts_draft - 中间件在
BeforeRouter阶段执行,此时this.Ctx.Input.Param还不可用,如需基于 URL 参数(如:id)做细粒度权限(例如“只能删自己创建的文章”),得退回到 Controller 的Prepare()里处理 - 多个中间件叠加时顺序敏感,权限中间件应放在日志、跨域等中间件之后,否则可能因 panic 导致权限失效
数据库表设计要支持「角色-权限-资源」三级解耦
别用一个 user.role 字段存字符串,也别把权限硬编码进代码里。Beego 本身不提供 RBAC 模型,得自己建表支撑动态权限变更。
最小可用三张表:
-
users:存用户基本信息,含user_id -
roles:存角色名,如role_id=1, name="admin" -
role_permissions:关联表,role_id+permission_code,例如(1, "post:delete")
关键设计点:
-
permission_code建议用冒号分隔的资源+操作格式,如"user:update"、"system:config:read",方便按前缀批量授权(如所有system:开头的权限给 superadmin) - 不要为每个接口单独建 permission 记录,而是按业务能力抽象,比如
"order:refund:approve"比"POST /api/orders/refund/approve"更易维护 - 用户登录后,把该用户所有
permission_code加载进 Session 或本地缓存(如map[string]bool),避免每次请求都查库
Controller.Prepare() 里做行级权限必须手动取参数
URL 中带 ID 的操作(如 DELETE /api/articles/42)常需行级控制:“用户只能删自己发布的文章”。这时光靠角色不够,得查数据库比对 article.AuthorID == currentUser.ID。
Beego 的 this.Ctx.Input.Param(":id") 在 Prepare() 里可用,但要注意:
-
:id是字符串,需转成整型再查库,别直接拼 SQL,防注入 - 如果 Controller 方法签名是
func (c *ArticleController) Delete(),this.Ctx.Input.Param(":id")返回的是路由中捕获的值;但如果用了beego.RESTful,且方法名是Delete,Beego 会自动调用Delete(),此时Param仍有效 - 查库失败(如文章不存在)应返回 404,而不是 403 —— 403 暗示“你知道这资源存在但没权限”,而 404 是“你甚至不该知道它存在”,后者更安全
真正麻烦的是嵌套资源,比如 /api/projects/5/tasks/22,既要校验用户有项目 5 的 project:read 权限,又要确认 task 22 确属该项目。这种逻辑堆在 Prepare() 里容易失控,建议封装成 CheckResourceOwner(c *Controller, resourceType string, id int) bool 工具函数复用。











