引言
在企业级应用中,权限管理是不可或缺的核心功能。一个完善的权限系统需要同时控制页面访问、路由跳转和功能按钮三个层面。本文将介绍基于 RBAC(Role-Based Access Control)模型的完整权限管理方案,并提供可直接落地的代码实现。
RBAC 模型基础
RBAC 的核心思想是:用户 → 角色 → 权限。用户不直接拥有权限,而是通过角色间接获取。
// 权限类型定义
type Permission = {
id: string;
code: string; // 权限标识,如 'user:create'
name: string; // 权限名称,如 '创建用户'
type: 'menu' | 'button' | 'api';
};
// 角色定义
type Role = {
id: string;
code: string; // 角色标识,如 'admin'
name: string; // 角色名称,如 '管理员'
permissions: Permission[];
};
// 用户定义
type User = {
id: string;
username: string;
roles: Role[];
};
路由权限控制
路由权限是最外层的防护,确保用户无法访问未授权的页面。
1. 路由元信息配置
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
requiresAuth: true,
permission: 'dashboard:view'
}
},
{
path: '/user',
name: 'UserManagement',
component: () => import('@/views/user/Index.vue'),
meta: {
requiresAuth: true,
permission: 'user:view'
},
children: [
{
path: 'create',
component: () => import('@/views/user/Create.vue'),
meta: { permission: 'user:create' }
}
]
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
2. 路由守卫实现
// router/permission.ts
import router from './index';
import { useAuthStore } from '@/stores/auth';
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next({
path: '/login',
query: { redirect: to.fullPath }
});
return;
}
if (to.meta.permission) {
const hasPermission = authStore.hasPermission(to.meta.permission as string);
if (!hasPermission) {
next('/403');
return;
}
}
if (to.path === '/dashboard') {
authStore.generateMenuFromRoutes();
}
next();
});
3. 权限 Store 实现
// stores/auth.ts
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as User | null,
permissions: new Set<string>(),
menuTree: [] as MenuNode[]
}),
getters: {
isAuthenticated: (state) => !!state.user,
hasPermission: (state) => (code: string) => {
return state.permissions.has(code);
},
hasAnyPermission: (state) => (codes: string[]) => {
return codes.some(code => state.permissions.has(code));
},
hasAllPermissions: (state) => (codes: string[]) => {
return codes.every(code => state.permissions.has(code));
}
},
actions: {
extractPermissions(user: User) {
const codes = new Set<string>();
user.roles.forEach(role => {
role.permissions.forEach(perm => {
codes.add(perm.code);
});
});
this.permissions = codes;
},
generateMenuFromRoutes() {
const routes = router.getRoutes();
this.menuTree = routes
.filter(route => route.meta?.permission)
.filter(route => this.hasPermission(route.meta.permission as string))
.map(route => ({
path: route.path,
name: route.name,
icon: route.meta.icon
}));
}
}
});
按钮权限控制
按钮权限通过自定义指令和组件实现细粒度控制。
1. 自定义指令 v-permission
// directives/permission.ts
import { useAuthStore } from '@/stores/auth';
import type { Directive, DirectiveBinding } from 'vue';
export const permission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const authStore = useAuthStore();
const { value } = binding;
if (value) {
const hasPermission = Array.isArray(value)
? authStore.hasAnyPermission(value)
: authStore.hasPermission(value);
if (!hasPermission) {
el.parentNode?.removeChild(el);
}
} else {
throw new Error('v-permission 需要传入权限标识');
}
}
};
2. 权限组件
<!-- components/Permission.vue -->
<template>
<slot v-if="hasPermission" />
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useAuthStore } from '@/stores/auth';
const props = defineProps<{
code: string | string[];
type?: 'any' | 'all';
}>();
const authStore = useAuthStore();
const hasPermission = computed(() => {
const codes = Array.isArray(props.code) ? props.code : [props.code];
if (props.type === 'all') {
return authStore.hasAllPermissions(codes);
}
return authStore.hasAnyPermission(codes);
});
</script>
3. 使用示例
<template>
<div class="user-management">
<el-button v-permission="'user:create'">
新增用户
</el-button>
<el-button v-permission="['user:edit', 'user:delete']">
批量操作
</el-button>
<Permission code="user:delete">
<el-button type="danger">删除</el-button>
</Permission>
</div>
</template>
API 权限校验
前端权限只是第一道防线,后端必须校验所有敏感操作。
// utils/request.ts
import axios from 'axios';
const request = axios.create({
baseURL: '/api',
timeout: 10000
});
request.interceptors.request.use(config => {
const authStore = useAuthStore();
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`;
}
return config;
});
request.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 403) {
ElMessage.error('权限不足,无法执行此操作');
}
return Promise.reject(error);
}
);
完整示例:用户管理页面
<!-- views/user/Index.vue -->
<template>
<div class="user-page">
<el-card>
<template #header>
<div class="header">
<span>用户管理</span>
<el-button
v-permission="'user:create'"
type="primary"
@click="handleCreate"
>
新增用户
</el-button>
</div>
</template>
<el-table :data="userList">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="role" label="角色" />
<el-table-column label="操作" width="200">
<template #default="{ row }">
<Permission code="user:edit">
<el-button size="small" @click="handleEdit(row)">
编辑
</el-button>
</Permission>
<Permission code="user:delete">
<el-button size="small" type="danger" @click="handleDelete(row)">
删除
</el-button>
</Permission>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { userApi } from '@/api/user';
const userList = ref([]);
const loadUsers = async () => {
try {
userList.value = await userApi.getList();
} catch (error) {
ElMessage.error('加载用户列表失败');
}
};
const handleDelete = async (row) => {
try {
await userApi.delete(row.id);
ElMessage.success('删除成功');
loadUsers();
} catch (error) {
ElMessage.error('删除失败');
}
};
onMounted(() => {
loadUsers();
});
</script>
总结
完善的权限管理系统需要三个层面的配合:
- 路由权限:控制页面访问,防止未授权访问
- 按钮权限:控制功能操作,实现细粒度管控
- API 权限:后端最终校验,确保数据安全
实施建议:
- 权限标识统一命名规范(模块:操作)
- 前端权限仅用于 UX 优化,不可依赖
- 定期审计权限分配,遵循最小权限原则
- 敏感操作记录审计日志
通过这套方案,可以构建安全、灵活、易维护的企业级权限系统。