Vue 3 + TypeScript + Element Plus 后台管理系统中的权限管理与鉴权

权限管理是后台管理系统中非常重要的一部分,确保用户能够基于其角色访问指定的资源,同时防止越权操作。一个完善的权限管理方案应该包括多层次的权限控制,从 Token 鉴权菜单、路由、按钮权限控制,并且要确保系统的灵活性和扩展性。以下是基于 Vue 3 + TypeScript + Element Plus 的权限管理的详细技术方案。


1. Token 鉴权与权限列表的缓存

1.1 Token 鉴权

Token 鉴权是前后端分离应用的常见认证方式。通常,用户登录后,后端会根据用户的身份信息生成一个 Token(如 JWT),并返回给前端。前端将该 Token 保存在浏览器中(如 localStoragesessionStorage),并在每次发起请求时通过 HTTP 请求头携带该 Token,后端验证 Token 的有效性,判断是否允许访问对应的资源。

登录过程
  1. 用户提交用户名和密码进行登录。
  2. 后端验证用户名和密码的正确性,如果验证通过,生成 Token(如 JWT)并返回给前端。
  3. 前端将 Token 存储在 localStoragesessionStorage 中,并在后续请求时通过 Authorization 请求头传递 Token。
  4. 后端每次收到请求时,验证 Token 是否有效,并根据验证结果返回相应的资源。
示例代码:获取 Token
// 登录成功后获取 token 并存储
const login = async (username: string, password: string) => {
  const response = await axios.post('/api/login', { username, password });
  const token = response.data.token;
  localStorage.setItem('token', token);
};
 
// 请求拦截器:在每个请求中附带 Token
axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

1.2 权限列表的缓存

为了减少每次请求时都需要重新从后台获取权限数据,前端通常会缓存用户的权限列表(如角色权限、路由权限、菜单权限等)。缓存可以存储在 VuexPinia 状态管理工具中,或者 localStorage / sessionStorage 中。

缓存的数据结构

  • routes:用户可访问的路由列表。
  • menus:用户可访问的菜单项。
  • buttons:用户可执行的按钮操作。
// 角色权限和路由权限的缓存存储
const storePermissions = (permissions: Permission[]) => {
  const routes = permissions.filter(p => p.type === 'menu').map(p => p.code);
  const buttons = permissions.filter(p => p.type === 'button').map(p => p.code);
  
  // 缓存到 localStorage
  localStorage.setItem('routes', JSON.stringify(routes));
  localStorage.setItem('buttons', JSON.stringify(buttons));
};

2. 菜单权限与路由权限控制

2.1 菜单权限

菜单权限控制通常依赖于用户的角色,系统会根据用户权限动态生成菜单。前端根据权限列表渲染相应的菜单项。

步骤

  1. 后端返回用户角色的权限列表(包括菜单权限)。
  2. 前端根据该权限列表动态渲染菜单项。
  3. 如果用户没有该权限,菜单项不会显示。

示例代码:动态菜单渲染

<template>
  <el-menu>
    <el-menu-item v-for="item in menuItems" :key="item.code" :index="item.code">
      <router-link :to="item.path">{{ item.name }}</router-link>
    </el-menu-item>
  </el-menu>
</template>
 
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { usePermissionStore } from '@/stores/permission';
 
const menuItems = ref([]);
 
onMounted(() => {
  const permissionStore = usePermissionStore();
  menuItems.value = permissionStore.routes.filter(route => route.type === 'menu');
});
</script>

2.2 路由权限控制

路由权限控制通过路由守卫(beforeEach)来实现。系统会根据用户的权限来动态加载路由,并阻止用户访问无权限的页面。

步骤

  1. 根据用户权限动态生成路由。
  2. 在路由守卫中,验证用户是否有权限访问目标路由。

示例代码:路由权限控制

import { createRouter, createWebHistory } from 'vue-router';
import { usePermissionStore } from './stores/permission';
 
const routes = [
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { permission: 'view_dashboard' },
  },
  {
    path: '/users',
    name: 'Users',
    component: () => import('@/views/Users.vue'),
    meta: { permission: 'view_users' },
  },
];
 
const router = createRouter({
  history: createWebHistory(),
  routes,
});
 
router.beforeEach((to, from, next) => {
  const permissionStore = usePermissionStore();
  const requiredPermission = to.meta.permission;
  
  if (requiredPermission && !permissionStore.routes.includes(requiredPermission)) {
    next({ name: 'Unauthorized' });
  } else {
    next();
  }
});
 
export default router;

3. 按钮权限控制

按钮权限控制是指对于页面中的每个操作按钮(如编辑、删除等),根据用户权限来决定是否显示或者是否可点击。

步骤

  1. 用户的权限列表中包含具体的按钮权限标识。
  2. 根据权限控制按钮的显示与隐藏。

示例代码:按钮权限控制

<template>
  <el-button v-if="hasPermission('edit_user')" @click="editUser">编辑</el-button>
  <el-button v-if="hasPermission('delete_user')" @click="deleteUser">删除</el-button>
</template>
 
<script setup lang="ts">
import { usePermissionStore } from '@/stores/permission';
 
const permissionStore = usePermissionStore();
 
const hasPermission = (permission: string) => {
  return permissionStore.buttons.includes(permission);
};
</script>

4. 数据权限控制

数据权限控制是指用户访问的具体数据也需要根据权限进行筛选。例如,一个管理员可以查看所有用户的数据,而普通用户只能查看自己的数据。

4.1 数据权限设计

  • 后端根据用户的角色和权限筛选数据。
  • 前端发起请求时,附带用户的权限信息,后端根据权限返回数据。

4.2 示例代码:请求数据时附带权限

const getUserData = async () => {
  const response = await axios.get('/api/user-data', {
    params: {
      permission: localStorage.getItem('role_permission'),
    },
  });
  return response.data;
};

5. 总结

通过结合 Token 鉴权 和基于角色的 权限管理,可以实现灵活而高效的前端权限控制。整个系统的权限控制包括:

  1. Token 鉴权:验证用户身份,确保合法用户访问资源。
  2. 菜单权限:动态生成用户可访问的菜单项。
  3. 路由权限:控制用户能访问的路由。
  4. 按钮权限:控制用户对页面中按钮的操作权限。
  5. 数据权限:通过 API 请求,控制用户能访问的具体数据。

这种设计方案能够有效地管理和控制不同角色、不同权限的用户访问,同时保持前端与后端的灵活扩展性。