前端权限控制与动态路由

场景题: “现有一个大型后台管理系统,包含了超级管理员、运营、财务三个角色。不同角色登录后:

  1. 左侧可看到的菜单栏不同。
  2. 即使手动在地址栏输入某些页面的 URL,如果没有权限也应当被拦截。
  3. 页面内的一个【删除配置】按钮,仅限超级管理员展示。 请说明如果你作为前端 Owner,你将主导落地怎样的前端权限控制方案?”

前端权限控制三板斧

要答好这道题,必须将权限分层为路由级视图级接口级来进行阐述。

1. 路由权限(核心:动态路由 Dynamic Routing)

千万不要在前端把所有的配置路由全写死,然后依靠在组件顶层加 if (role !== 'admin') return <Forbidden />; 来拦截。这是极其不安全的,且会导致首屏打包的 Bundle 无比臃肿。

主流最佳实践:静态路由 + 动态拉取注册

  1. 静态路由:前端代码中只硬编码写死任何人都能访问的基础页面:如 /login/404/403
  2. 鉴权过程
    • 用户登录拿到 Token。
    • 带着 Token 发送获取用户信息的请求,后端返回该用户的角色(Role)或该用户所属的权限树(Permissions)
  3. 动态生成路由表
    • 前端维护一份全部受控路由配置表的字典,每个路由打上了角色标签(比如 meta: { roles: ['admin', 'editor'] })。
    • 根据后端返回的角色,去这个大字典里做 .filter()
    • 利用 Vue-Router 的 router.addRoute() 或 React-Router 的动态数组替换机制,将过滤后的安全路由真正在系统生效。
  4. 生成侧边栏:将过滤后的那份树形数据传递给 Menu 菜单组件渲染。不在树上的页面,不渲染,且用户硬在地址栏敲 URL 会因未注册路由直接被带到 404/403 页面,实现物理隔绝。

2. 元素/视图级权限(大粒度:指令/高阶组件)

路由权限只管页面,如果两个角色都能进同一个页面,但“删除”按钮只能超管点击呢?

  • Vue 生态:自定义指令 v-auth

    <!-- 只有含有 delete_user 权限流的用户才会渲染这个 DOM -->
    <button v-auth="['admin', 'delete_user']">删除用户</button>

    底层在 v-authmounted 阶段,去获取全局 Vuex 的权限点数组。如果没命中,直接通过 el.parentNode.removeChild(el) 从真实 DOM 层面把这个按钮阉割掉(不能用 display: none,容易被控制台破解)。

  • React 生态:高阶包装组件 Auth

    <Auth requireRoles={['admin']}>
      <button>删除用户</button>
    </Auth>

    底层检测 Context 里的角色信息,不匹配直接返回 null 不进行渲染。

3. 请求接口级权限(最后防线)

不论前端如何做 addRoute 或者是防君子不防小人的 v-auth,防篡改的最后一道城墙永远在后端。 如果恶意用户通过抓包直接向发起了针对无权限 POST /api/delete 的 HTTP Request: 服务端必须校验 Token 所属角色,直接打回 401 Unauthorized403 Forbidden。前端响应拦截器(Interceptor)中统一捕获 401/403 错误,并强退登出或无情弹窗报错。


面试高分对答指北

Q:如果系统极大,按角色分配权限不够灵活(比如你们既有运营,又有内容审核员,权限交叉严重),怎么办?

A(满分反杀设计架构): 这就说明基于简单的 RBAC(Role-Based Access Control,基于角色的访问控制) 瓶颈出现了。 这时前端的路由配置表中不再应该写死 roles: ['admin'],而是采用更细粒度的资源权限码 (Action Code)

前端和后端对齐一套权限码,如 PAGE_USER_LIST, BTN_USER_EDIT。 后端直接发给前端一个他所拥有的无边界的 Code 数组:['PAGE_USER_LIST', 'BTN_USER_EDIT', ...]。 前端此时抛开具体的“职位头衔词汇”,只纯粹检测这个权限库中有没有对应的 Code 码即可。把给员工组合分配职权的操作,在后台界面交还给产品的超管自由装配即可。