Revel 框架通过 InterceptFunc 实现统一 RBAC 控制:权限校验必须在 init() 中注册拦截器,使用静态 authMap 映射 "controller.action" 到角色,Session 存 Role 枚举值防伪造,Action 名需拼接控制器全名。

Revel 框架本身不内置 RBAC(基于角色的访问控制),但它的拦截器(InterceptFunc)机制和 Controller 生命周期设计,让权限控制可以干净地解耦到统一入口。关键不是“能不能做”,而是“怎么组织才不容易漏、不重复、不绕弯”。
InterceptFunc 是权限检查的唯一入口点
所有 Controller Action 调用前都会经过 app.Init 中注册的拦截器链。权限校验必须放在这里,而不是每个 Action 里手写 if role != AdminRole { return c.Redirect(...) } —— 那样极易遗漏、难维护。
- 必须在
app/init.go的init()函数中注册:revel.Interceptors.Insert(checkAuthentication, revel.BEFORE, &revel.Controller{}) -
checkAuthentication必须返回revel.Result:若权限不足,直接返回c.Redirect(...)或c.RenderError(403),后续 Action 不会执行 - 不要依赖
defer或After拦截器做权限拒绝——它们发生在 Action 执行之后,可能已造成副作用(如数据库写入、日志落盘)
角色与 Action 权限映射必须静态初始化,不能运行时查 DB
authMap 是一个 map[string]Role,键为 "controller.action" 格式(如 "account.Login"),值为允许的最低角色。
- 初始化必须在包级
init()函数中完成,确保在路由加载前就绪 - 不要用
database.QueryRow动态加载权限配置——这会让每次请求都多一次 DB 查询,且无法热更新(revel的热编译不重载全局变量) - 示例:
func init() { authMap = make(map[string]Role) authMap["account.Login"] = AnonymousRole authMap["admin.Dashboard"] = AdminRole authMap["post.Edit"] = UserRole } - 注意大小写:
c.Action默认带首字母大写("Login"),别写成"login"导致匹配失败
Session 中的角色存储必须可靠且防篡改
revel.Session 底层是加密 cookie,但你存什么、怎么存,决定了安全性上限。
- 存储键名要固定且私有,比如
CSessionRole,避免和业务字段冲突 - 值只能是预定义的
Role枚举(AnonymousRole/UserRole/AdminRole),绝不能存字符串 "admin" 或数字 999 —— 否则攻击者可伪造 session 修改角色 - 登录成功后必须显式赋值:
c.Session[CSessionRole] = AdminRole // ✅ 安全 c.Session[CSessionRole] = "admin" // ❌ 危险,绕过类型检查
- 登出时务必清空:
delete(c.Session, CSessionRole),不要只设为AnonymousRole
最容易被忽略的点:Action 名称解析依赖控制器命名空间
c.Action 只返回方法名(如 "Index"),而 authMap 键是 "controller.action" 全路径。你必须拼接完整标识符:
- 正确方式:
controllerName := reflect.TypeOf(*c).Elem().Name() actionKey := strings.ToLower(controllerName + "." + c.Action)
- 错误方式(仅用
c.Action):所有控制器的Login方法会全部映射到同一个 key,导致权限错乱 - 如果用了嵌套控制器(如
admin.UserController),reflect.TypeOf(*c).Elem().Name()仍只返回"UserController",需结合c.Request.URL.Path或自定义字段补全命名空间
权限逻辑看似简单,但真正上线后出问题的,90% 都卡在 Action 名称拼接错误、session 值未校验类型、或拦截器注册顺序不对——这些地方没日志、不报错,只默默放行或拒绝。


















