2024-07-28 14:29:05 +08:00
|
|
|
|
---
|
|
|
|
|
outline: deep
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 权限
|
|
|
|
|
|
|
|
|
|
框架内置了两种权限控制方式:
|
|
|
|
|
|
|
|
|
|
- 通过用户角色来判断菜单或者按钮是否可以访问
|
|
|
|
|
- 通过接口来判断菜单或者按钮是否可以访问
|
|
|
|
|
|
|
|
|
|
## 前端访问控制
|
|
|
|
|
|
|
|
|
|
**实现原理**: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoutes` 添加到路由实例,实现权限的过滤。
|
|
|
|
|
|
|
|
|
|
**缺点**: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统
|
|
|
|
|
|
|
|
|
|
### 步骤
|
|
|
|
|
|
|
|
|
|
- 确保当前模式为前端访问控制模式
|
|
|
|
|
|
|
|
|
|
调整对应应用目录下的`preferences.ts`,确保`accessMode='frontend'`。
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { defineOverridesPreferences } from '@vben/preferences';
|
|
|
|
|
|
|
|
|
|
export const overridesPreferences = defineOverridesPreferences({
|
|
|
|
|
// overrides
|
|
|
|
|
app: {
|
|
|
|
|
// 默认值,可不填
|
|
|
|
|
accessMode: 'frontend',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 配置路由权限
|
|
|
|
|
|
|
|
|
|
**如果不配置,默认可见**
|
|
|
|
|
|
|
|
|
|
```ts {3}
|
|
|
|
|
{
|
|
|
|
|
meta: {
|
|
|
|
|
authority: ['super'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 确保接口返回的角色和路由表的权限匹配
|
|
|
|
|
|
2024-07-30 21:10:28 +08:00
|
|
|
|
可查看应用下的 `src/store/auth`,找到下面代码,
|
2024-07-28 14:29:05 +08:00
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// 设置登录用户信息,需要确保 userInfo.roles 是一个数组,且包含路由表中的权限
|
|
|
|
|
// 例如:userInfo.roles=['super', 'admin']
|
2024-07-30 21:10:28 +08:00
|
|
|
|
authStore.setUserInfo(userInfo);
|
2024-07-28 14:29:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
到这里,就已经配置完成,你需要确保登录后,接口返回的角色和路由表的权限匹配,否则无法访问。
|
|
|
|
|
|
|
|
|
|
### 菜单可见,但禁止访问
|
|
|
|
|
|
|
|
|
|
有时候,我们需要菜单可见,但是禁止访问,可以通过下面的方式实现,设置 `menuVisibleWithForbidden` 为 `true`,此时菜单可见,但是禁止访问,会跳转403页面。
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
{
|
|
|
|
|
meta: {
|
|
|
|
|
menuVisibleWithForbidden: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 后端访问控制
|
|
|
|
|
|
|
|
|
|
**实现原理**: 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 router.addRoutes 添加到路由实例,实现权限的动态生成。
|
|
|
|
|
|
2024-08-01 20:50:45 +08:00
|
|
|
|
**缺点**: 后端需要提供符合规范的数据结构,前端需要处理数据结构,适合权限较为复杂的系统。
|
2024-07-28 14:29:05 +08:00
|
|
|
|
|
|
|
|
|
### 步骤
|
|
|
|
|
|
2024-08-01 20:50:45 +08:00
|
|
|
|
- 确保当前模式为后端访问控制模式
|
2024-07-28 14:29:05 +08:00
|
|
|
|
|
|
|
|
|
调整对应应用目录下的`preferences.ts`,确保`accessMode='backend'`。
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { defineOverridesPreferences } from '@vben/preferences';
|
|
|
|
|
|
|
|
|
|
export const overridesPreferences = defineOverridesPreferences({
|
|
|
|
|
// overrides
|
|
|
|
|
app: {
|
|
|
|
|
accessMode: 'backend',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 确保接口返回的菜单数据结构正确
|
|
|
|
|
|
|
|
|
|
可查看应用下的 `src/router/access.ts`,找到下面代码,
|
|
|
|
|
|
|
|
|
|
```ts {5}
|
|
|
|
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|
|
|
|
return await generateAccessible(preferences.app.accessMode, {
|
|
|
|
|
fetchMenuListAsync: async () => {
|
|
|
|
|
// 这个接口为后端返回的菜单数据
|
|
|
|
|
return await getAllMenus();
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 接口返回菜单数据,可看注释说明
|
|
|
|
|
|
|
|
|
|
::: details 接口返回菜单数据示例
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const dashboardMenus = [
|
|
|
|
|
{
|
|
|
|
|
// 这里固定写死 BasicLayout,不可更改
|
|
|
|
|
component: 'BasicLayout',
|
|
|
|
|
meta: {
|
|
|
|
|
order: -1,
|
|
|
|
|
title: 'page.dashboard.title',
|
|
|
|
|
},
|
|
|
|
|
name: 'Dashboard',
|
|
|
|
|
path: '/',
|
|
|
|
|
redirect: '/analytics',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Analytics',
|
|
|
|
|
path: '/analytics',
|
|
|
|
|
// 这里为页面的路径,需要去掉 views/ 和 .vue
|
|
|
|
|
component: '/dashboard/analytics/index',
|
|
|
|
|
meta: {
|
|
|
|
|
affixTab: true,
|
|
|
|
|
title: 'page.dashboard.analytics',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Workspace',
|
|
|
|
|
path: '/workspace',
|
|
|
|
|
component: '/dashboard/workspace/index',
|
|
|
|
|
meta: {
|
|
|
|
|
title: 'page.dashboard.workspace',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
|
|
到这里,就已经配置完成,你需要确保登录后,接口返回的菜单格式正确,否则无法访问。
|
|
|
|
|
|
|
|
|
|
## 按钮细粒度控制
|
|
|
|
|
|
|
|
|
|
在某些情况下,我们需要对按钮进行细粒度的控制,我们可以借助接口或者角色来控制按钮的显示。
|
|
|
|
|
|
|
|
|
|
### 权限码
|
|
|
|
|
|
2024-07-30 21:10:28 +08:00
|
|
|
|
权限码为接口返回的权限码,通过权限码来判断按钮是否显示,逻辑在`src/store/auth`下:
|
2024-07-28 14:29:05 +08:00
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
|
|
|
|
fetchUserInfo(),
|
|
|
|
|
getAccessCodes(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
userInfo = fetchUserInfoResult;
|
2024-07-30 21:10:28 +08:00
|
|
|
|
authStore.setUserInfo(userInfo);
|
|
|
|
|
accessStore.setAccessCodes(accessCodes);
|
2024-07-28 14:29:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
找到 `getAccessCodes` 对应的接口,可根据业务逻辑进行调整。
|
|
|
|
|
|
|
|
|
|
权限吗返回的数据结构为字符串数组,例如:`['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']`
|
|
|
|
|
|
|
|
|
|
有了权限码,就可以使用 `@vben/access` 提供的`AccessControl`组件及API来进行按钮的显示与隐藏。
|
|
|
|
|
|
|
|
|
|
#### 组件方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { AccessControl, useAccess } from '@vben/access';
|
|
|
|
|
|
|
|
|
|
const { accessMode, hasAccessByCodes } = useAccess();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- 需要指明 type="code" -->
|
|
|
|
|
<AccessControl :codes="['AC_100100']" type="code">
|
|
|
|
|
<Button> Super 账号可见 ["AC_1000001"] </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['AC_100030']" type="code">
|
|
|
|
|
<Button> Admin 账号可见 ["AC_100010"] </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['AC_1000001']" type="code">
|
|
|
|
|
<Button> User 账号可见 ["AC_1000001"] </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['AC_100100', 'AC_100010']" type="code">
|
|
|
|
|
<Button> Super & Admin 账号可见 ["AC_100100","AC_1000001"] </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
</template>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### API方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { AccessControl, useAccess } from '@vben/access';
|
|
|
|
|
|
|
|
|
|
const { hasAccessByCodes } = useAccess();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<Button v-if="hasAccessByCodes(['AC_100100'])">
|
|
|
|
|
Super 账号可见 ["AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button v-if="hasAccessByCodes(['AC_100030'])">
|
|
|
|
|
Admin 账号可见 ["AC_100010"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button v-if="hasAccessByCodes(['AC_1000001'])">
|
|
|
|
|
User 账号可见 ["AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_1000001'])">
|
|
|
|
|
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
</template>
|
|
|
|
|
```
|
|
|
|
|
|
2024-08-01 20:58:21 +08:00
|
|
|
|
#### 指令方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<template>
|
|
|
|
|
<Button class="mr-4" v-access:code="['AC_100100']">
|
|
|
|
|
Super 账号可见 ["AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button class="mr-4" v-access:code="['AC_100030']">
|
|
|
|
|
Admin 账号可见 ["AC_100010"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button class="mr-4" v-access:code="['AC_1000001']">
|
|
|
|
|
User 账号可见 ["AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
<Button class="mr-4" v-access:code="['AC_100100', 'AC_1000001']">
|
|
|
|
|
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
|
|
|
|
</Button>
|
|
|
|
|
</template>
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-28 14:29:05 +08:00
|
|
|
|
### 角色
|
|
|
|
|
|
|
|
|
|
角色判断方式不需要接口返回的权限码,直接通过角色来判断按钮是否显示。
|
|
|
|
|
|
|
|
|
|
#### 组件方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { AccessControl } from '@vben/access';
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<AccessControl :codes="['super']">
|
|
|
|
|
<Button> Super 角色可见 </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['admin']">
|
|
|
|
|
<Button> Admin 角色可见 </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['user']">
|
|
|
|
|
<Button> User 角色可见 </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
<AccessControl :codes="['super', 'admin']">
|
|
|
|
|
<Button> Super & Admin 角色可见 </Button>
|
|
|
|
|
</AccessControl>
|
|
|
|
|
</template>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### API方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { useAccess } from '@vben/access';
|
|
|
|
|
|
|
|
|
|
const { hasAccessByRoles } = useAccess();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<Button v-if="hasAccessByRoles(['super'])"> Super 账号可见 </Button>
|
|
|
|
|
<Button v-if="hasAccessByRoles(['admin'])"> Admin 账号可见 </Button>
|
|
|
|
|
<Button v-if="hasAccessByRoles(['user'])"> User 账号可见 </Button>
|
|
|
|
|
<Button v-if="hasAccessByRoles(['super', 'admin'])">
|
|
|
|
|
Super & Admin 账号可见
|
|
|
|
|
</Button>
|
|
|
|
|
</template>
|
|
|
|
|
```
|
2024-08-01 20:58:21 +08:00
|
|
|
|
|
|
|
|
|
#### 指令方式
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<template>
|
|
|
|
|
<Button class="mr-4" v-access:role="['super']"> Super 角色可见 </Button>
|
|
|
|
|
<Button class="mr-4" v-access:role="['admin']"> Admin 角色可见 </Button>
|
|
|
|
|
<Button class="mr-4" v-access:role="['user']"> User 角色可见 </Button>
|
|
|
|
|
<Button class="mr-4" v-access:role="['super', 'admin']">
|
|
|
|
|
Super & Admin 角色可见
|
|
|
|
|
</Button>
|
|
|
|
|
</template>
|
|
|
|
|
```
|