企业级前端权限系统设计全解:从动态路由到按钮级控制

209 阅读2分钟

一、权限系统为什么难做?

大多数人以为权限就是“控制能不能访问页面”,但企业级项目(尤其如广告平台)涉及的是:

  • 路由权限
  • 页面元素权限(按钮/操作)
  • 接口权限
  • 数据权限(作用域)
  • 权限变更后的同步与清除

一个真正健壮的权限系统,需要“动态+精细+可维护”,否则权限绕过、漏判、混乱就是家常便饭。


二、权限模型抽象

我们抽象出三类权限:

interface Permission {
  menus: string[];           // 路由菜单权限
  elements: string[];        // 按钮/组件权限
  actions: string[];         // 接口操作权限
}

服务端返回格式:

{
  "menus": ["/ad-plan", "/material"],
  "elements": ["ad:create", "ad:delete"],
  "actions": ["CREATE_AD", "DELETE_AD"]
}

三、动态路由权限

  1. 登录后拉取权限信息:
const res = await getUserPermissions()
const { menus, elements, actions } = res.data
  1. 动态构造前端路由表:
// 全量路由表
const allRoutes = [
  { path: '/ad-plan', meta: { permission: '/ad-plan' }, component: AdPlan },
  { path: '/material', meta: { permission: '/material' }, component: Material }
]

const filteredRoutes = allRoutes.filter(r => menus.includes(r.meta.permission))
filteredRoutes.forEach(route => router.addRoute(route))

四、按钮级权限控制(元素级)

封装自定义指令 v-permission

app.directive('permission', {
  mounted(el, binding) {
    const userStore = useUserStore()
    if (!userStore.elements.includes(binding.value)) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  }
})

使用方式:

<n-button v-permission="'ad:delete'">删除</n-button>

或统一封装权限按钮组件:

<PermissionButton code="ad:create">新建</PermissionButton>

五、接口权限控制(行为级)

接口访问前自动附加权限信息,用于后端二次校验。

Axios 拦截器注入请求标识:

axios.interceptors.request.use(config => {
  config.headers['X-User-Actions'] = userStore.actions.join(',')
  return config
})

六、权限变更与缓存更新

问题:某用户被移除某权限后,再次访问仍能看到旧页面或按钮?

解决:权限变更后需彻底刷新客户端状态:

const reloadPermission = async () => {
  const res = await getUserPermissions()
  userStore.$patch(res.data)
  resetRoutes()              // 移除旧权限路由
  generateRoutes()           // 重新添加新路由
  router.replace('/dashboard')
}

七、常见权限问题与解法

1. 用户手动输入 URL 越权访问?

➡ 使用导航守卫拦截:

router.beforeEach((to, _, next) => {
  if (!userStore.menus.includes(to.path)) {
    return next('/403')
  }
  next()
})

2. 刷新页面权限丢失?

➡ 将权限缓存到 localStorage,刷新后恢复

const init = () => {
  const cached = localStorage.getItem('PERMISSION')
  if (cached) userStore.$patch(JSON.parse(cached))
  else fetchPermissions()
}

3. 多角色用户权限重叠时如何取交集?

➡ 后端应返回融合后的权限,前端无需合并逻辑


八、权限系统测试建议

权限系统容易遗漏,因此建议写权限单元测试或 E2E:

it('should not render button if no permission', () => {
  userStore.elements = []
  const wrapper = mount(Component)
  expect(wrapper.find('button').exists()).toBe(false)
})

九、总结:权限系统四字诀:可查、可配、可控、可变

  • 可查:日志记录、定位问题
  • 可配:权限点集中配置,利于管理
  • 可控:页面、接口、组件都有校验逻辑
  • 可变:支持权限变更实时响应

权限系统并不难,但如果做得浅,很容易成为系统最大的安全漏洞源头。