Skip to content

权限集成 ​

概述 ​

前端权限体系围绕「登录拿到令牌 → 拉取权限信息 → 生成可访问菜单/路由 → 页面与按钮级鉴权」这条主线展开。它建立在 Vben Admin 的 @vben/access、@vben/stores 能力之上,并与后端的 RBAC(角色-菜单-权限)模型天然对接。

权限信息分为三层:

  • 菜单/路由权限:决定用户能看到哪些页面、能进入哪些路由。
  • 角色(roles):用户拥有的角色标识集合,用于前端路由过滤(frontend 模式)与展示。
  • 权限码(permissions):形如 模块:资源:操作(如 system:user:create)的细粒度字符串,用于按钮级鉴权。

权限信息的下发流程 ​

登录成功后,前端通过一次接口调用把「用户 + 角色 + 权限码 + 菜单」一并拉回,并写入对应的 Pinia Store,后续鉴权全部基于这些本地缓存,不再频繁请求后端。

核心接口与数据来源:

  • 登录:POST /system/auth/login,返回令牌(见 apps/web-ele/src/api/core/auth.ts)。
  • 权限信息:GET /system/auth/get-permission-info,由 getAuthPermissionInfoApi() 封装,返回结构即 AuthPermissionInfo:
ts
interface AuthPermissionInfo {
  user: UserInfo;        // 用户信息,含 homePath 首页地址
  roles: string[];       // 角色标识集合
  permissions: string[]; // 权限码集合
  menus: AppRouteRecordRaw[]; // 后端下发的菜单(路由元信息)
}

写入逻辑位于 apps/web-ele/src/store/auth.ts 的 fetchUserInfo 中:

ts
userStore.setUserInfo(authPermissionInfo.user);
userStore.setUserRoles(authPermissionInfo.roles);
accessStore.setAccessMenus(authPermissionInfo.menus);
accessStore.setAccessCodes(authPermissionInfo.permissions);

accessToken、refreshToken、accessCodes 等会通过 Pinia 持久化插件落地到本地存储,刷新页面后无需重新登录即可恢复鉴权上下文。


相关的状态 Store ​

Store关键字段 / 方法作用
useAccessStore (core-access)accessToken、accessCodes、accessMenus、accessRoutes、isAccessChecked存放令牌、权限码、可访问菜单与路由,及是否已生成过动态路由
useUserStore (core-user)userInfo、userRoles存放用户资料与角色标识

这两个 Store 都来自 @vben/stores,业务代码中可直接引入:

ts
import { useAccessStore, useUserStore } from '@vben/stores';

路由与菜单的生成 ​

菜单与路由的生成发生在路由守卫中,由 apps/web-ele/src/router/guard.ts 的 setupAccessGuard 驱动:

  1. 校验 accessToken,缺失则跳转登录页(带 redirect 回跳地址)。
  2. 若 isAccessChecked 已为 true,直接放行,避免重复生成。
  3. 调用 fetchUserInfo 拉取最新权限信息(若尚未加载)。
  4. 调用 generateAccess({ roles, router, routes }) 生成可访问的菜单与路由。
  5. 将结果写入 accessStore,并把 isAccessChecked 置为 true,最后重定向到首页或目标页。

生成策略由偏好配置 accessMode 决定(项目默认值为 backend,见 apps/web-ele/src/preferences.ts):

  • backend:最常用。前端仅维护一份基础路由骨架,页面菜单由后端的 get-permission-info 返回,权限集中在后端配置。
  • frontend:路由在前端写死(在 meta.roles 上声明可见角色),适合菜单结构固定、仅做角色过滤的场景。
  • mixed:二者结合,后端菜单为主、前端路由补充。

若某路由 meta.menuVisibleWithForbidden = true,它会在菜单中可见,但访问时会被重定向到 403 页面(forbiddenComponent)。


按钮级鉴权 ​

路由权限解决了「能不能进页面」,按钮权限解决「页面里能不能点」。框架提供三种方式,按场景选择。

useAccess 组合式函数 ​

在 <script setup> 中通过 useAccess() 获取鉴权方法:

ts
import { useAccess } from '@vben/access';

const { hasAccessByCodes, hasAccessByRoles } = useAccess();

// 判断当前用户是否拥有指定权限码(任一满足即可)
const canCreate = hasAccessByCodes(['system:user:create']);

// 判断当前用户是否拥有指定角色
const isAdmin = hasAccessByRoles(['ADMIN']);

hasAccessByCodes 与 hasAccessByRoles 都是「传入集合中存在任一匹配即返回 true」的或逻辑,便于多个权限/角色共用一个入口。

v-access 指令 ​

用于整块 DOM 的显隐控制:无权限时该元素会被直接从 DOM 中移除。指令在 bootstrap.ts 启动时全局注册,使用时无需 import。

语法:

vue
<!-- 按权限码控制(backend 模式的主力用法) -->
<el-button v-access:code="['system:user:create']">新增</el-button>

<!-- 按角色控制(仅 frontend 模式生效;backend 模式下 role 也会走权限码判断) -->
<el-button v-access:role="['ADMIN']">管理</el-button>

v-access 接收一个数组或单个字符串,值为权限码或角色标识。典型业务页面(如 views/infra/demo/general/demo03)中的新增、导出、删除按钮即采用此写法:

vue
<el-button v-access:code="['infra:demo03-student:create']">新增</el-button>
<el-button v-access:code="['infra:demo03-student:export']">导出</el-button>
<el-button v-access:code="['infra:demo03-student:delete']">删除</el-button>

AccessControl 组件 ​

当需要在「有权限时渲染某些内容、无权限时渲染兜底内容」的场景下使用,适合配合 <template #default> 与 <template #fallback> 插槽(或条件渲染):

vue
<script setup lang="ts">
import { AccessControl } from '@vben/access';
</script>

<template>
  <AccessControl :codes="['system:user:update']" type="code">
    <el-button>编辑</el-button>
  </AccessControl>
</template>

type 可选 code(默认)或 role,codes 为权限码/角色数组。


权限码命名约定 ​

项目统一采用 模块:资源:操作 三段式,例如:

  • system:user:create —— 系统模块的「用户」资源的「新增」操作
  • infra:demo03-student:export —— 基础设施演示模块的「学生」资源导出

这套命名与后端 RBAC 的权限标识完全一致:后端在 get-permission-info 中返回用户所拥有的权限码集合,前端只需用 hasAccessByCodes 或 v-access:code 做包含判断即可,前后端无需额外映射。


开发实践建议 ​

  • 页面入口用路由权限,页内操作按钮用权限码:菜单由后端统一收口,按钮通过 v-access:code 声明,二者职责清晰。
  • 优先使用指令而非手动 v-if:v-access 会自动移除无权限节点,避免残留不可点击的灰色按钮暴露业务结构。
  • 不要在前端做「唯一」的安全校验:前端鉴权只解决体验问题(隐藏/禁用),真正的越权拦截必须由后端接口层的权限校验保证。
  • 新增菜单需在后端配置:在 backend 模式下,没有在后端菜单中登记的页面即使写好了前端代码,也不会出现在可访问路由里。
  • 切换账号后注意重置:登录与登出都会通过 resetAllStores 清理 Store,确保不同权限的用户之间不会串号。