前端权限控制实践:基于 RBAC 的完整落地方案
关键词: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
权限点的设计建议用 资源:操作 的字符串格式,语义清晰,便于后端做注解式鉴权。
// 登录后后端返回的用户信息(关键字段)
{
roles: ['admin', 'editor'],
permissions: ['user:add', 'user:edit', 'role:view', 'dashboard:view']
}
三、四层权限控制
1. 菜单权限
登录后拿到的 permissions 是扁平数组。菜单配置里给每个节点挂一个 permission 字段,渲染前做一次过滤即可:
// 递归过滤无权限菜单
function filterMenu(menuList, permissions) {
return menuList.filter(item => {
if (item.permission && !permissions.includes(item.permission)) return false;
if (item.children) item.children = filterMenu(item.children, permissions);
return true;
});
}
2. 路由权限(最重要)
弱方案:把所有路由写死,进页面再判权限。
强方案:动态路由。只挂载基础路由(登录、404、403),拿到权限后再 addRoutes 追加受控路由。用户手输一个无权限 URL,直接跳 403,杜绝"能进空白页"的尴尬。
// 权限守卫(伪代码)
function guard(to, from, next) {
if (to.meta.permission && !store.permissions.includes(to.meta.permission)) {
next('/403');
} else {
next();
}
}
3. 按钮级权限
菜单和路由是"粗粒度",按钮是"细粒度"。推荐封装一个 <Auth> 组件或自定义 hook:
function Auth({ permission, children }) {
const { permissions } = useAuth();
return permissions.includes(permission) ? children : null;
}
// 使用
<Auth permission="user:add">
<Button type="primary">新增用户</Button>
</Auth>
4. 接口/数据权限
前端能做的有限,但至少要在请求层统一拦截:无权限接口直接不发起,或根据返回 code 做统一提示,避免重复写 if (res.code === 403)。
四、几个踩过的坑
- 刷新丢失状态:权限信息存 Vuex/Redux,刷新就没了。要么存 localStorage(注意敏感信息),要么刷新后用 token 重新拉取用户信息。
- 动态路由重复添加:路由实例要缓存初始路由,每次重新计算时先重置,否则热更新或重复登录会叠加路由。
- 权限语义混乱:别把"角色"当"权限"用。
v-if="role==='admin'"是反模式,业务一变就改代码。永远判断permission,角色只作为权限的集合。
五、总结
一套好的前端权限方案 = RBAC 模型 + 动态路由 + 菜单过滤 + 按钮组件 + 请求拦截。它不保证安全,但能让"该看到的人看到,不该看到的人根本不知道存在"。
如果你也在做后台管理,建议从第一天就把权限抽象成 permission 维度,后期几乎零成本扩展。
如果对你有帮助,点赞收藏不迷路。下一篇讲《高并发选课系统的前端架构设计》,关注我持续更新。