前端权限控制 路由守卫与按钮级权限
星堡王子
4 分钟阅读
前端权限控制 路由守卫与按钮级权限
前端权限不是安全边界,而是体验与噪音过滤。真正的安全在后端,前端控制只解决两件事:不该进的页面别让入口出现,不该点的操作别把按钮亮给用户。把前端权限当成安全机制是常见误解,它会让人放松对后端校验的投入,留下越权漏洞。
路由守卫:进入前拦截
页面级权限靠路由守卫,在导航真正发生前判断当前用户是否拥有目标路由要求的角色。路由表把权限要求写在元信息里,守卫读元信息做决策:
const routes = [
{ path: '/admin', component: Admin, meta: { roles: ['admin'] } },
{ path: '/report', component: Report, meta: { roles: ['admin', 'analyst'] } },
];
router.beforeEach((to) => {
const userRoles = store.getRoles();
if (to.meta.roles && !to.meta.roles.some(r => userRoles.includes(r))) {
return '/403';
}
});
这里返回 /403 是中断并跳转,而不是渲染目标页。边界:守卫里拿角色要等用户信息就绪,常见坑是首屏守卫在用户数据还没返回时就执行,导致有权限的人被误拦。做法是在应用启动阶段先拉取权限,再挂载路由或放行守卫。还要注意动态路由:后端下发的可访问菜单必须再做校验,不能前端自己拼路由,否则等于把权限决定权交给了客户端,改个本地状态就能看到本不该看的页面骨架。
按钮级权限:粒度到操作
路由守卫管不住"同页面里某些人只能看不能改"。按钮级权限把控制粒度下放到操作,用权限码描述能力,再决定元素是否渲染或可用:
function can(action) {
return store.getPermissions().includes(action);
}
// 模板里
{can('user:export') && <button>导出</button>}
更通用的做法是封装一个 <Auth perm="user:export"> 组件,命中的才渲染子节点,避免每个按钮手写判断。这样权限变更只改权限码映射,UI 逻辑不动。边界:如果只是 disabled 而不是移除按钮,用户仍能通过控制台改属性触发点击,所以按钮级权限同样只是体验层,真正的拦截在接口。对于"看到但不能点"和"看不到"两种需求要区分,前者用 disabled,后者用 v-if/条件渲染。
与后端的关系
前端隐藏按钮、拦截路由,都只是"别让人误操作",任何人都能打开控制台改 DOM 或直连接口绕过。因此权限码必须从后端接口获取,不能硬编码在前端;并且每个被保护的接口必须自己再做一次鉴权,前端拦截只是后端契约之上的体验层。经验上,最稳妥的模型是"角色 -> 权限码 -> 接口校验"三层,前端只消费权限码决定展示,后端用同一套权限码做最终裁决,前后端共用一份定义避免错位。
边界
- 权限码要可枚举、可解释,避免用"魔法字符串"散落各处,建议集中成常量并配文档。
- 角色与权限分离:直接绑角色会让权限调整牵一发而动全身,绕一层权限码更灵活,新增角色只是重新组合码。
- 无权限时的兜底要统一(跳 403 / 隐藏 / 置灰),别让不同页面表现不一致造成困惑。
- 权限变更后要做响应式更新,尤其是用户在已打开页面时被管理员改了角色,应当有机制刷新前端权限状态而不是停留在旧状态。
经验上,前端权限的准确定位是"让正确的用户看到正确的界面",它提升的是体验和清晰度,而不是安全性。安全这件事,永远只能由后端兜底。
浏览 0
点赞 0
评论 0