前端权限控制实践:基于 RBAC 的完整落地方案

文章来源声明: 原文作者:这锅我不背哈; 来源站点:掘金; 原文链接:https://juejin.cn/post/7685195161455116322; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

四层权限拆解清晰,动态路由与权限语义反模式的提醒尤其实用。适合正在搭建后台管理系统的前端团队作为落地方案参考。

前端权限控制实践:基于 RBAC 的完整落地方案 ------------------------

关键词:RBAC、动态路由、细粒度权限、React、Ant Design

一、为什么要做前端权限控制

很多同学会问:权限不应该是后端的事吗?后端校验了,前端为什么还要管?

答案是:前端权限不是为了安全(安全永远靠后端),而是为了体验和边界。 一个普通用户点了半天"删除"按钮,提交后被后端一个 403 弹窗打回来,体验极差。更合理的做法是:用户根本没有这个按钮、看不到这个菜单、进不了这个路由。

本文基于我在一个企业级后台(React + UmiJS + Ant Design + DVA)中的真实落地经验,讲清楚一套可复用的 RBAC 前端权限方案。

二、权限模型:RBAC

RBAC(Role-Based Access Control)的核心就三张表:

  • 用户(User) → 绑定一个或多个角色(Role)
  • 角色(Role) → 绑定多个权限点(Permission)
  • 权限点(Permission) → 对应一个具体的操作,如 user:addrole:edit

权限点的设计建议用 资源:操作 的字符串格式,语义清晰,便于后端做注解式鉴权。

<span>// 登录后后端返回的用户信息(关键字段)</span>
{
  <span>roles</span>: [<span>'admin'</span>, <span>'editor'</span>],
  <span>permissions</span>: [<span>'user:add'</span>, <span>'user:edit'</span>, <span>'role:view'</span>, <span>'dashboard:view'</span>]
}

三、四层权限控制

1. 菜单权限

登录后拿到的 permissions 是扁平数组。菜单配置里给每个节点挂一个 permission 字段,渲染前做一次过滤即可:

<span>// 递归过滤无权限菜单</span>
<span>function</span> <span>filterMenu</span>(<span>menuList, permissions</span>) {
  <span>return</span> menuList.<span>filter</span>(<span><span>item</span> =></span> {
    <span>if</span> (item.<span>permission</span> && !permissions.<span>includes</span>(item.<span>permission</span>)) <span>return</span> <span>false</span>;
    <span>if</span> (item.<span>children</span>) item.<span>children</span> = <span>filterMenu</span>(item.<span>children</span>, permissions);
    <span>return</span> <span>true</span>;
  });
}

2. 路由权限(最重要)

弱方案:把所有路由写死,进页面再判权限。 强方案:动态路由。只挂载基础路由(登录、404、403),拿到权限后再 addRoutes 追加受控路由。用户手输一个无权限 URL,直接跳 403,杜绝"能进空白页"的尴尬。

<span>// 权限守卫(伪代码)</span>
<span>function</span> <span>guard</span>(<span>to, <span>from</span>, next</span>) {
  <span>if</span> (to.<span>meta</span>.<span>permission</span> && !store.<span>permissions</span>.<span>includes</span>(to.<span>meta</span>.<span>permission</span>)) {
    <span>next</span>(<span>'/403'</span>);
  } <span>else</span> {
    <span>next</span>();
  }
}

3. 按钮级权限

菜单和路由是"粗粒度",按钮是"细粒度"。推荐封装一个 <Auth> 组件或自定义 hook:

<span>function</span> <span>Auth</span>(<span>{ permission, children }</span>) {
  <span>const</span> { permissions } = <span>useAuth</span>();
  <span>return</span> permissions.<span>includes</span>(permission) ? children : <span>null</span>;
}

<span>// 使用</span>
<<span>Auth</span> permission=<span>"user:add"</span>>
  <span><span><<span>Button</span> <span>type</span>=<span>"primary"</span>></span>新增用户<span></<span>Button</span>></span></span>
</<span>Auth</span>>

4. 接口/数据权限

前端能做的有限,但至少要在请求层统一拦截:无权限接口直接不发起,或根据返回 code 做统一提示,避免重复写 if (res.code === 403)

四、几个踩过的坑

  1. 刷新丢失状态:权限信息存 Vuex/Redux,刷新就没了。要么存 localStorage(注意敏感信息),要么刷新后用 token 重新拉取用户信息。
  2. 动态路由重复添加:路由实例要缓存初始路由,每次重新计算时先重置,否则热更新或重复登录会叠加路由。
  3. 权限语义混乱:别把"角色"当"权限"用。v-if="role==='admin'" 是反模式,业务一变就改代码。永远判断 permission,角色只作为权限的集合。

五、总结

一套好的前端权限方案 = RBAC 模型 + 动态路由 + 菜单过滤 + 按钮组件 + 请求拦截。它不保证安全,但能让"该看到的人看到,不该看到的人根本不知道存在"。

如果你也在做后台管理,建议从第一天就把权限抽象成 permission 维度,后期几乎零成本扩展。


如果对你有帮助,点赞收藏不迷路。下一篇讲《高并发选课系统的前端架构设计》,关注我持续更新。