各位观众老爷们,晚上好!今天咱们不聊风花雪月,来点硬核的——Vue 权限管理系统实战。保证听完,腰不酸了,腿不疼了,一口气能写十个权限控制模块!
一、开场白:为啥要有权限管理?
想象一下,你辛辛苦苦开发的网站,没做任何限制,谁都能进来瞎搞,把数据删了,把页面改了,甚至把服务器都弄崩溃了……这酸爽,想想都可怕!
所以,权限管理就像给你的网站装了一扇扇门,只有拥有对应钥匙的人才能进入,保证安全又可靠。
二、权限管理系统的核心要素
一个合格的权限管理系统,至少要考虑以下几个方面:
- 用户(User): 谁在使用系统?
- 角色(Role): 用户有什么身份?(比如管理员、普通用户、访客等)
- 权限(Permission): 角色能干什么?(比如查看页面、修改数据、删除文件等)
- 资源(Resource): 权限作用于什么地方?(比如某个路由、某个按钮、某个数据字段等)
它们之间的关系可以用一句话概括:用户属于角色,角色拥有权限,权限作用于资源。
三、Vue 权限管理系统架构设计
我们的目标是打造一个通用的权限管理系统,支持路由权限、按钮级权限、数据权限,并且能动态与后端 API 集成。因此,架构设计如下:
- 前端(Vue):
- 使用 Vuex 管理用户角色和权限信息。
- 利用 Vue Router 的
beforeEach
钩子进行路由权限控制。 - 自定义指令实现按钮级权限控制。
- 封装 API 请求,根据权限过滤数据。
- 后端(API):
- 提供用户登录接口,返回用户角色和权限信息。
- 提供动态权限配置接口,方便修改权限。
- 提供数据接口,根据用户权限返回相应的数据。
四、实战:代码说话
废话不多说,直接上代码!
1. Vuex 管理权限信息
首先,我们需要一个 Vuex store 来存储用户的角色和权限信息。
// store/modules/permission.js
import { constantRoutes } from '@/router' // 引入静态路由
import { getPermissionList } from '@/api/user' // 引入获取权限列表的接口
const state = {
routes: [], // 存储用户可访问的路由
asyncRoutes: [] // 存储异步路由
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.asyncRoutes = routes
state.routes = constantRoutes.concat(routes) // 将静态路由和异步路由合并
}
}
const actions = {
// 获取用户权限列表
generateRoutes({ commit }, roles) {
return new Promise((resolve, reject) => {
getPermissionList(roles).then(response => {
const accessedRoutes = filterAsyncRoutes(response.data.permissions, asyncRoutes) // 根据权限过滤异步路由
commit('SET_ROUTES', accessedRoutes) // 将过滤后的路由设置到 state 中
resolve(accessedRoutes)
}).catch(error => {
reject(error)
})
})
}
}
// 过滤异步路由
function filterAsyncRoutes(permissions, routes) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(permissions, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(permissions, tmp.children)
}
res.push(tmp)
}
})
return res
}
// 判断路由是否有权限
function hasPermission(permissions, route) {
if (route.meta && route.meta.roles) {
return permissions.some(permission => route.meta.roles.includes(permission)) // 判断用户的权限是否包含路由的角色
} else {
return true // 如果路由没有设置角色,则默认拥有权限
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
// asyncRoutes 异步路由配置 (示例)
export const asyncRoutes = [
{
path: '/permission',
component: () => import('@/views/permission/index'),
meta: { roles: ['admin', 'editor'] }, // 只有 admin 和 editor 角色才能访问
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
meta: { roles: ['admin'] } // 只有 admin 角色才能访问
},
{
path: 'directive',
component: () => import('@/views/permission/directive'),
meta: { roles: ['admin', 'editor'] } // 只有 admin 和 editor 角色才能访问
}
]
},
// ... 其他路由
{ path: '*', redirect: '/404', hidden: true }
]
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import permission from './modules/permission'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
permission
}
})
2. 路由权限控制
利用 Vue Router 的 beforeEach
钩子,判断用户是否拥有访问某个路由的权限。
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store' // 引入 Vuex store
import { constantRoutes, asyncRoutes } from './routes'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: constantRoutes // 初始加载静态路由
})
router.beforeEach(async (to, from, next) => {
const hasToken = localStorage.getItem('token') // 假设 token 存在 localStorage 中
if (hasToken) {
if (to.path === '/login') {
// 如果已登录,则重定向到首页
next({ path: '/' })
} else {
const hasRoutes = store.getters.routes && store.getters.routes.length > 0
if (hasRoutes) {
next() // 如果已经动态添加过路由,则直接放行
} else {
try {
// 获取用户角色信息 (这里假设角色信息存储在 localStorage 中,实际应从后端获取)
const roles = JSON.parse(localStorage.getItem('userInfo')).roles;
// 根据角色生成可访问的路由表
await store.dispatch('permission/generateRoutes', roles)
// 动态添加可访问路由
router.addRoutes(store.getters.routes)
// hack方法 确保addRoutes已完成
// 设置replace: true,这样导航就不会留下历史记录
next({ ...to, replace: true })
} catch (error) {
// 清除token
localStorage.removeItem('token')
localStorage.removeItem('userInfo');
next(`/login?redirect=${to.path}`)
console.log(error)
}
}
}
} else {
// 如果没有token
if (to.path === '/login') {
next() // 如果是登录页,则直接放行
} else {
// 重定向到登录页
next(`/login?redirect=${to.path}`)
}
}
})
export default router
3. 按钮级权限控制
通过自定义指令,判断用户是否拥有某个按钮的权限,如果没有,则隐藏按钮。
// directives/permission.js
import Vue from 'vue'
import store from '@/store'
Vue.directive('permission', {
inserted(el, binding) {
const { value } = binding
const permissions = JSON.parse(localStorage.getItem('userInfo')).permissions; // 从 localStorage 中获取权限列表 (实际应从 Vuex store 中获取)
if (value && value instanceof Array && value.length > 0) {
const hasPermission = permissions.some(permission => value.includes(permission));
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el) // 移除没有权限的元素
}
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
})
使用方法:
<template>
<div>
<button v-permission="['user:create']">创建用户</button>
<button v-permission="['user:update']">修改用户</button>
<button v-permission="['user:delete']">删除用户</button>
</div>
</template>
4. 数据权限控制
数据权限控制是指,根据用户的角色和权限,返回不同的数据。这需要在后端 API 中实现。
例如,假设我们有一个用户列表接口,后端可以根据用户的角色,返回不同的用户数据:
- 管理员(admin): 返回所有用户数据。
- 普通用户(user): 只能返回自己的用户数据。
前端在调用 API 时,不需要做任何特殊处理,只需要拿到数据并展示即可。
5. 与后端 API 动态集成
为了实现动态权限配置,我们需要后端 API 提供以下接口:
- 获取用户角色和权限信息: 在用户登录时调用,返回用户的角色和权限列表。
- 修改权限: 管理员可以调用此接口,修改用户的角色和权限。
- 获取权限列表: 用于前端展示权限列表,方便管理员配置权限。
前端在获取到权限信息后,需要更新 Vuex store 中的数据,并重新计算路由。
五、代码示例:后端 API (Node.js + Express)
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const port = 3000;
app.use(cors());
app.use(bodyParser.json());
// 模拟数据库
const users = [
{ id: 1, username: 'admin', password: 'password', roles: ['admin'], permissions: ['user:create', 'user:update', 'user:delete'] },
{ id: 2, username: 'editor', password: 'password', roles: ['editor'], permissions: ['user:update'] },
{ id: 3, username: 'user', password: 'password', roles: ['user'], permissions: [] },
];
const products = [
{ id: 1, name: 'Product A', price: 100 },
{ id: 2, name: 'Product B', price: 200 },
{ id: 3, name: 'Product C', price: 300 },
];
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
res.json({
code: 200,
data: {
id: user.id,
username: user.username,
roles: user.roles,
permissions: user.permissions,
token: 'fake_token' // 实际项目中应生成 JWT
}
});
} else {
res.status(401).json({ code: 401, message: 'Invalid credentials' });
}
});
// 获取用户权限列表接口
app.get('/permissionList', (req, res) => {
// 实际项目中应从数据库中获取权限列表
const permissions = ['user:create', 'user:update', 'user:delete', 'product:read', 'product:write'];
res.json({ code: 200, data: { permissions } });
});
// 获取用户角色权限列表接口
app.get('/user/permissionList', (req, res) => {
const roles = req.query.roles;
const user = users.find(u => u.roles.includes(roles));
if(user){
res.json({ code: 200, data: { permissions:user.permissions } });
}else{
res.status(401).json({ code: 401, message: 'Invalid roles' });
}
});
// 获取产品列表接口 (模拟数据权限)
app.get('/products', (req, res) => {
const token = req.headers.authorization; // 实际项目中应验证 JWT
// 这里简单模拟,根据 token (用户角色) 返回不同的数据
// 在实际项目中,需要根据用户的角色和权限,查询数据库并返回相应的数据
if (token === 'fake_token') {
// 假设 admin 角色拥有所有权限
res.json({ code: 200, data: products });
} else {
// 其他角色只能看到部分数据
res.json({ code: 200, data: products.slice(0, 2) });
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
六、权限管理策略
选择合适的权限管理策略至关重要。常见的策略有:
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
基于角色的访问控制 (RBAC) | 简单易用,易于理解和管理,适合大型系统,可以根据角色快速分配权限。 | 当角色数量较多时,权限管理可能会变得复杂。 | 适用于用户角色相对稳定,权限需求不经常变化的系统。 |
基于属性的访问控制 (ABAC) | 灵活性强,可以根据各种属性(用户属性、资源属性、环境属性等)进行权限控制。 | 配置复杂,需要对各种属性进行定义和管理,性能可能受到影响。 | 适用于需要精细化权限控制,权限需求经常变化的系统。 |
访问控制列表 (ACL) | 粒度细,可以对单个资源进行权限控制。 | 管理复杂,当资源数量较多时,权限管理会变得非常困难。 | 适用于资源数量较少,需要对单个资源进行精细化权限控制的系统。 |
选择哪种策略,需要根据你的实际情况进行考虑。
七、注意事项
- 安全第一: 永远不要相信前端传来的数据,所有权限判断都应该在后端进行。
- 缓存: 权限数据可以进行缓存,提高性能。但要注意缓存更新的问题。
- 测试: 充分测试你的权限管理系统,确保没有漏洞。
- 文档: 编写清晰的文档,方便其他开发人员使用和维护。
八、总结
一个好的权限管理系统,能够有效地保护你的网站安全,提高开发效率。希望今天的讲座对你有所帮助。记住,权限管理不是一蹴而就的,需要不断地学习和实践。
散会!