设计并实现一个 Vue 权限管理系统,包括路由守卫、按钮级权限控制和数据权限过滤。

各位靓仔靓女们,晚上好!今天咱们聊聊Vue权限管理,这玩意儿,说白了就是给你的网站或者应用设置一些门槛,让不同的人看到不同的东西,做不同的事情。别怕,我尽量用大白话把这事儿给讲清楚,保证你听完能撸起袖子就开干。

一、权限管理是个啥?为啥要搞它?

想象一下,你开了一家银行,里面有各种各样的钱(数据),还有各种各样的柜员(用户)。

  • 柜员A: 只能存钱和取钱,不能看老板的账本(数据权限)。
  • 柜员B: 除了存取钱,还能办理贷款业务(按钮权限)。
  • 老板: 想看啥看啥,想干啥干啥(超级管理员)。

这就是权限管理!它能保证:

  • 数据安全: 防止敏感信息泄露。
  • 功能控制: 避免用户误操作或者恶意破坏。
  • 合规性: 满足一些行业规范的要求。

二、Vue权限管理的三板斧:路由守卫、按钮权限、数据权限

Vue权限管理,通常包括这三部分:

  1. 路由守卫: 控制用户能访问哪些页面。
  2. 按钮权限: 控制用户能点击哪些按钮。
  3. 数据权限: 控制用户能看到哪些数据。

咱们一个一个来,先来最基础的路由守卫。

三、路由守卫:站岗的保安

路由守卫就像站岗的保安,检查用户是否有权限进入某个页面。 Vue Router 提供了 beforeEach 钩子,可以在路由跳转之前进行拦截。

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import Admin from '../views/Admin.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { requiresAuth: true } // 标记需要登录才能访问
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/admin',
    name: 'Admin',
    component: Admin,
    meta: { requiresAuth: true, requiredRole: 'admin' } //需要管理员权限
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('token') // 假设token存在就认为已登录
  const userRole = localStorage.getItem('role') // 假设存储了用户角色

  if (to.meta.requiresAuth) {
    if (!isAuthenticated) {
      // 未登录,跳转到登录页
      next('/login')
    } else {
      // 已登录,判断是否有角色权限
      if(to.meta.requiredRole && to.meta.requiredRole !== userRole){
        // 没有权限
        next('/'); // 重定向到首页或者403页面
      } else {
        next() // 允许访问
      }
    }
  } else {
    // 不需要登录的页面,直接允许访问
    next()
  }
})

export default router

代码解释:

  • meta 属性: 在路由配置中,我们用 meta 属性来标记哪些页面需要登录 (requiresAuth: true),还可以标记需要哪些角色 (requiredRole: 'admin')。
  • beforeEach 钩子: 每次路由跳转前都会执行这个钩子。
  • isAuthenticated: 这里简单地用 localStorage 是否存在 token 来判断用户是否已登录。实际项目中,你需要用更可靠的方式验证token,比如发送请求到后端验证。
  • userRole: 这里简单地用 localStorage 获取用户角色。实际项目中,需要在登录后从后端获取。
  • next(): next() 函数控制路由跳转。
    • next(): 允许跳转到目标路由。
    • next('/login'): 跳转到登录页。
    • next('/'): 跳转到首页。

四、按钮权限:守卫按钮的小精灵

光能控制页面还不够,有些按钮,比如“删除”按钮,不是谁都能点的。 这时候就需要按钮权限控制了。

方法一:指令 (Directive)

Vue的指令可以方便地操作DOM,我们可以自定义一个指令来控制按钮的显示与隐藏。

// directives/permission.js
export default {
  install(Vue) {
    Vue.directive('permission', {
      inserted(el, binding) {
        const requiredPermissions = binding.value // 获取指令绑定的权限标识
        const userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]')// 假设从localStorage获取用户权限列表

        if (!requiredPermissions) {
          return; // 没有权限要求,默认不显示
        }

        const hasPermission = Array.isArray(requiredPermissions)
            ? requiredPermissions.some(permission => userPermissions.includes(permission))
            : userPermissions.includes(requiredPermissions);

        if (!hasPermission) {
          el.parentNode.removeChild(el) // 没有权限,移除按钮
          // 或者 el.disabled = true;  // 禁用按钮
        }
      }
    })
  }
}
// main.js
import Vue from 'vue'
import App from './App.vue'
import PermissionDirective from './directives/permission'

Vue.use(PermissionDirective)

new Vue({
  render: h => h(App),
}).$mount('#app')
// ExampleComponent.vue
<template>
  <div>
    <button v-permission="'user.create'">创建用户</button>
    <button v-permission="'user.edit'">编辑用户</button>
    <button v-permission="['user.delete', 'article.delete']">删除用户/文章</button>
  </div>
</template>

<script>
export default {
  mounted() {
    // 模拟从后端获取用户权限列表
    localStorage.setItem('permissions', JSON.stringify(['user.edit', 'article.delete']));
  }
}
</script>

代码解释:

  • v-permission 指令: 自定义的指令,用于控制按钮的显示与隐藏。
  • binding.value: 指令绑定的值,也就是需要的权限标识。
  • localStorage.getItem('permissions'):localStorage 获取用户拥有的权限列表。 实际项目中,你需要从后端获取。
  • el.parentNode.removeChild(el): 如果没有权限,直接从 DOM 中移除按钮。 也可以使用 el.disabled = true 禁用按钮。
  • 权限标识: 这里的权限标识可以是字符串,也可以是字符串数组。如果是数组,只要用户拥有其中一个权限,就允许显示按钮。

方法二:组件 (Component)

另一种方式是使用组件来包裹按钮,根据权限决定是否渲染组件。

// components/PermissionButton.vue
<template>
  <button v-if="hasPermission">
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    permission: {
      type: [String, Array],
      required: true
    }
  },
  computed: {
    hasPermission() {
      const requiredPermissions = this.permission;
      const userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]');// 假设从localStorage获取用户权限列表

      if (!requiredPermissions) {
        return false; // 没有权限要求,默认不显示
      }

      return Array.isArray(requiredPermissions)
          ? requiredPermissions.some(permission => userPermissions.includes(permission))
          : userPermissions.includes(requiredPermissions);
    }
  }
}
</script>
// ExampleComponent.vue
<template>
  <div>
    <PermissionButton permission="user.create">创建用户</PermissionButton>
    <PermissionButton permission="user.edit">编辑用户</PermissionButton>
    <PermissionButton permission="['user.delete', 'article.delete']">删除用户/文章</PermissionButton>
  </div>
</template>

<script>
import PermissionButton from './components/PermissionButton.vue'

export default {
  components: {
    PermissionButton
  },
  mounted() {
    // 模拟从后端获取用户权限列表
    localStorage.setItem('permissions', JSON.stringify(['user.edit', 'article.delete']));
  }
}
</script>

这个方法和指令类似,只是把权限判断的逻辑封装到了一个组件里,更加灵活。

五、数据权限:数据筛选的魔法棒

数据权限控制用户能看到哪些数据,比如只能看到自己部门的数据,或者只能看到指定状态的数据。

方法一:后端过滤 (推荐)

最安全可靠的方式是在后端进行数据过滤。前端只负责展示后端返回的数据。 这样做的好处是:

  • 安全: 防止前端绕过权限直接访问数据。
  • 高效: 减少数据传输量,提高性能。

示例:

假设后端 API 接口返回用户列表,但是需要根据用户的角色进行过滤。

  • 普通用户: 只能看到自己的信息。
  • 管理员: 可以看到所有用户的信息。

后端代码(伪代码):

# Python (Flask)
from flask import Flask, jsonify, request

app = Flask(__name__)

users = [
    {'id': 1, 'name': '张三', 'department': '技术部'},
    {'id': 2, 'name': '李四', 'department': '销售部'},
    {'id': 3, 'name': '王五', 'department': '技术部'}
]

@app.route('/users')
def get_users():
    user_role = request.headers.get('X-User-Role') # 从请求头获取用户角色
    user_id = request.headers.get('X-User-Id')     # 从请求头获取用户ID

    if user_role == 'admin':
        # 管理员,返回所有用户
        return jsonify(users)
    else:
        # 普通用户,只返回自己的信息
        user = next((u for u in users if u['id'] == int(user_id)), None)
        if user:
            return jsonify([user])
        else:
            return jsonify([])

if __name__ == '__main__':
    app.run(debug=True)

前端代码:

// ExampleComponent.vue
<template>
  <div>
    <ul>
      <li v-for="user in users" :key="user.id">{{ user.name }} - {{ user.department }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      users: []
    }
  },
  mounted() {
    this.fetchUsers()
  },
  methods: {
    async fetchUsers() {
      const token = localStorage.getItem('token');
      const role = localStorage.getItem('role');
      const userId = localStorage.getItem('userId');

      axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; // 设置token
      axios.defaults.headers.common['X-User-Role'] = role;  // 设置用户角色
      axios.defaults.headers.common['X-User-Id'] = userId;    // 设置用户ID

      try {
        const response = await axios.get('/users') // 假设后端接口是/users
        this.users = response.data
      } catch (error) {
        console.error('获取用户列表失败', error)
      }
    }
  }
}
</script>

代码解释:

  • 后端: 根据用户角色和ID,对用户数据进行过滤。
  • 前端: 在请求头中携带用户角色和ID,方便后端进行权限判断。
  • axios.defaults.headers.common: 设置 axios 的全局请求头,方便每次请求都携带用户信息。

方法二:前端过滤 (不推荐,仅用于简单场景)

在某些简单的场景下,也可以在前端进行数据过滤。 但是! 这种方式非常不安全,因为用户可以通过修改前端代码绕过权限。

// ExampleComponent.vue
<template>
  <div>
    <ul>
      <li v-for="user in filteredUsers" :key="user.id">{{ user.name }} - {{ user.department }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, name: '张三', department: '技术部' },
        { id: 2, name: '李四', department: '销售部' },
        { id: 3, name: '王五', department: '技术部' }
      ],
      userRole: 'normal', // 假设用户角色是普通用户
      userId: 1 // 假设用户ID是1
    }
  },
  computed: {
    filteredUsers() {
      if (this.userRole === 'admin') {
        // 管理员,返回所有用户
        return this.users
      } else {
        // 普通用户,只返回自己的信息
        return this.users.filter(user => user.id === this.userId)
      }
    }
  }
}
</script>

千万记住: 前端过滤只能用于展示,不能用于涉及到敏感数据的操作。

六、总结:权限管理的最佳实践

  • 后端权限验证是王道: 所有涉及到数据安全的操作,都必须在后端进行权限验证。
  • 权限标识要规范: 制定一套清晰的权限标识体系,方便管理和维护。 比如 user.createuser.editarticle.delete
  • 权限数据集中管理: 将用户的权限数据存储在后端数据库中,方便统一管理。
  • 灵活运用各种技术: 根据实际情况,灵活运用路由守卫、按钮权限、数据权限等技术。
  • 做好错误处理: 当用户没有权限访问某个页面或功能时,要给出友好的提示信息。

七、权限管理模型的选择:RBAC模型

在设计权限管理系统时,权限模型是一个非常重要的部分。常见的权限模型有:

  • ACL(Access Control List): 访问控制列表,直接将用户和资源进行关联,权限控制粒度细,但是管理复杂。
  • RBAC(Role-Based Access Control): 基于角色的访问控制,将用户和角色关联,角色和权限关联,简化了权限管理。
  • ABAC(Attribute-Based Access Control): 基于属性的访问控制,通过动态评估用户、资源和环境的属性来决定是否允许访问,灵活性高,但是实现复杂。

通常情况下,RBAC模型是比较适合大多数应用的。

RBAC模型的核心概念:

  • 用户(User): 系统的使用者。
  • 角色(Role): 一组权限的集合。
  • 权限(Permission): 允许执行的操作,例如:查看用户、编辑文章等。

RBAC模型的关系:

  • 用户被分配到一个或多个角色。
  • 角色被授予一个或多个权限。

RBAC模型的优点:

  • 简化权限管理:通过角色来管理权限,避免了直接将权限授予用户,降低了管理复杂度。
  • 提高灵活性:可以根据业务需求灵活调整角色和权限的对应关系。
  • 易于维护:当权限发生变化时,只需要修改角色的权限,而不需要修改每个用户的权限。

RBAC模型的实现:

  1. 数据库设计:

    表名 字段 类型 说明
    users id INT 用户ID (主键)
    username VARCHAR 用户名
    password VARCHAR 密码
    roles id INT 角色ID (主键)
    name VARCHAR 角色名称
    permissions id INT 权限ID (主键)
    name VARCHAR 权限名称
    user_roles user_id INT 用户ID (外键,关联users)
    role_id INT 角色ID (外键,关联roles)
    role_permissions role_id INT 角色ID (外键,关联roles)
    permission_id INT 权限ID (外键,关联permissions)
  2. 后端API设计:

    • 登录接口:验证用户身份,获取用户角色和权限信息。
    • 获取用户权限接口:根据用户ID获取用户拥有的权限列表。
    • 角色管理接口:创建、修改、删除角色。
    • 权限管理接口:创建、修改、删除权限。
    • 用户角色管理接口:为用户分配角色。
    • 角色权限管理接口:为角色分配权限。
  3. 前端实现:

    • 在登录后,从后端获取用户角色和权限信息,并存储在localStorage或Vuex中。
    • 使用路由守卫控制页面访问权限。
    • 使用指令或组件控制按钮权限。
    • 根据用户权限过滤数据。

总结:

RBAC模型是一种常用的权限管理模型,可以简化权限管理,提高灵活性,易于维护。 在实际项目中,可以根据业务需求选择合适的权限模型。

好了,今天的课就上到这里。希望大家能够掌握Vue权限管理的基本原理和实现方法,并在实际项目中灵活运用。 记住,权限管理不是一蹴而就的事情,需要不断学习和实践才能掌握。 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注