关键词:RBAC、动态路由、细粒度权限、React、Ant Design
一、为什么要做前端权限控制
很多同学会问:权限不应该是后端的事吗?后端校验了,前端为什么还要管?
答案是:前端权限不是为了安全(安全永远靠后端),而是为了体验和边界。 一个普通用户点了半天"删除"按钮,提交后被后端一个 403 弹窗打回来,体验极差。更合理的做法是:用户根本没有这个按钮、看不到这个菜单、进不了这个路由。
本文基于我在一个企业级后台(React + UmiJS + Ant Design + DVA)中的真实落地经验,讲清楚一套可复用的 RBAC 前端权限方案。
二、权限模型:RBAC
RBAC(Role-Based Access Control)的核心就三张表:
- 用户(User) → 绑定一个或多个角色(Role)
- 角色(Role) → 绑定多个权限点(Permission)
- 权限点(Permission) → 对应一个具体的操作,如
user:add、role: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)。
四、几个踩过的坑
- 刷新丢失状态:权限信息存 Vuex/Redux,刷新就没了。要么存 localStorage(注意敏感信息),要么刷新后用 token 重新拉取用户信息。
- 动态路由重复添加:路由实例要缓存初始路由,每次重新计算时先重置,否则热更新或重复登录会叠加路由。
- 权限语义混乱:别把"角色"当"权限"用。
v-if="role==='admin'"是反模式,业务一变就改代码。永远判断permission,角色只作为权限的集合。
五、总结
一套好的前端权限方案 = RBAC 模型 + 动态路由 + 菜单过滤 + 按钮组件 + 请求拦截。它不保证安全,但能让"该看到的人看到,不该看到的人根本不知道存在"。
如果你也在做后台管理,建议从第一天就把权限抽象成 permission 维度,后期几乎零成本扩展。
如果对你有帮助,点赞收藏不迷路。下一篇讲《高并发选课系统的前端架构设计》,关注我持续更新。
四层权限拆解清晰,动态路由与权限语义反模式的提醒尤其实用。适合正在搭建后台管理系统的前端团队作为落地方案参考。