Vue 3 管理后台权限系统设计 | 企业级最佳实践
🔐 Vue 3 全栈架构师 | 企业级权限系统专家

Vue 3 管理后台权限系统设计

基于 RBAC 模型的完整权限解决方案

🎯 权限系统架构设计

graph TB A[用户登录] --> B[权限认证] B --> C[路由守卫] C --> D[动态路由] D --> E[页面渲染] E --> F[组件权限] B --> B1[JWT Token验证] B --> B2[用户角色获取] C --> C1[路由元信息检查] C --> C2[权限拦截] D --> D1[菜单生成] D --> D2[路由注册] F --> F1[指令权限] F --> F2[功能权限] G[权限管理] --> H[角色管理] H --> I[权限分配] I --> J[数据同步]

🔐 认证 (Authentication)

用户身份验证,登录状态管理

🚦 授权 (Authorization)

访问权限控制,资源权限管理

📊 审计 (Audit)

操作日志记录,安全监控

🏗️ 项目目录结构

src/
├── permissions/           # 权限核心模块
│   ├── guards/           # 路由守卫
│   ├── directives/       # 权限指令
│   ├── utils/            # 权限工具
│   └── types/            # 类型定义
├── stores/               # 状态管理
│   └── auth.ts          # 认证状态
├── router/               # 路由配置
│   ├── index.ts         # 路由入口
│   ├── routes/          # 路由定义
│   └── permission.ts    # 权限路由
├── components/          # 权限组件
│   └── Permission/      # 权限相关组件
└── views/               # 页面组件
    └── admin/           # 管理后台页面

RBAC 数据模型设计

erDiagram USERS ||--o{ USER_ROLES : has ROLES ||--o{ ROLE_PERMISSIONS : has PERMISSIONS ||--o{ ROLE_PERMISSIONS : includes MENUS ||--o{ MENU_PERMISSIONS : requires USERS { string id PK string username string password string email datetime created_at } ROLES { string id PK string name string description boolean is_system } PERMISSIONS { string id PK string code string name string module string action } MENUS { string id PK string name string path string icon string parent_id number order }

🔑 认证系统实现

1. Pinia 认证状态管理

// stores/auth.ts
import { defineStore } from 'pinia'

interface User {
    id: string
    username: string
    email: string
    roles: string[]
    permissions: string[]
}

interface AuthState {
    user: User | null
    token: string | null
    isAuthenticated: boolean
}

export const useAuthStore = defineStore('auth', {
    state: (): AuthState => ({
        user: null,
        token: localStorage.getItem('token'),
        isAuthenticated: false
    }),

    getters: {
        hasPermission: (state) => (permission: string) => {
            return state.user?.permissions.includes(permission) ?? false
        },
        hasRole: (state) => (role: string) => {
            return state.user?.roles.includes(role) ?? false
        },
        hasAnyPermission: (state) => (permissions: string[]) => {
            return permissions.some(permission => 
                state.user?.permissions.includes(permission)
            )
        }
    },

    actions: {
        async login(credentials: { username: string; password: string }) {
            try {
                const response = await api.login(credentials)
                this.token = response.token
                this.user = response.user
                this.isAuthenticated = true
                
                localStorage.setItem('token', response.token)
                localStorage.setItem('user', JSON.stringify(response.user))
            } catch (error) {
                throw error
            }
        },

        async logout() {
            this.token = null
            this.user = null
            this.isAuthenticated = false
            localStorage.removeItem('token')
            localStorage.removeItem('user')
        },

        async checkAuth() {
            if (!this.token) return false
            
            try {
                const user = await api.getCurrentUser()
                this.user = user
                this.isAuthenticated = true
                return true
            } catch {
                this.logout()
                return false
            }
        }
    }
})

2. JWT 拦截器配置

// utils/request.ts
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'

const request = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL,
    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 === 401) {
            const authStore = useAuthStore()
            authStore.logout()
            window.location.href = '/login'
        }
        return Promise.reject(error)
    }
)

export default request

🚦 路由权限控制

1. 路由配置与元信息

// router/routes.ts
import type { RouteRecordRaw } from 'vue-router'

export const constantRoutes: RouteRecordRaw[] = [
    {
        path: '/login',
        name: 'Login',
        component: () => import('@/views/Login.vue'),
        meta: { 
            title: '登录',
            requiresAuth: false 
        }
    },
    {
        path: '/',
        component: () => import('@/layouts/DefaultLayout.vue'),
        redirect: '/dashboard',
        meta: { requiresAuth: true },
        children: [
            {
                path: 'dashboard',
                name: 'Dashboard',
                component: () => import('@/views/Dashboard.vue'),
                meta: { 
                    title: '仪表板',
                    icon: 'dashboard',
                    requiresAuth: true
                }
            }
        ]
    }
]

export const asyncRoutes: RouteRecordRaw[] = [
    {
        path: '/system',
        component: () => import('@/layouts/DefaultLayout.vue'),
        meta: { 
            title: '系统管理',
            icon: 'setting',
            requiresAuth: true,
            roles: ['admin']  // 需要管理员角色
        },
        children: [
            {
                path: 'users',
                name: 'UserManagement',
                component: () => import('@/views/system/UserManagement.vue'),
                meta: { 
                    title: '用户管理',
                    permissions: ['user:read', 'user:write']
                }
            },
            {
                path: 'roles',
                name: 'RoleManagement',
                component: () => import('@/views/system/RoleManagement.vue'),
                meta: { 
                    title: '角色管理',
                    permissions: ['role:read', 'role:write'],
                    roles: ['admin']
                }
            }
        ]
    }
]

2. 路由守卫实现

// router/permission.ts
import type { Router } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

export function setupPermissionGuard(router: Router) {
    router.beforeEach(async (to, from, next) => {
        const authStore = useAuthStore()
        
        // 检查是否需要认证
        if (to.meta.requiresAuth) {
            if (!authStore.isAuthenticated) {
                // 尝试重新认证
                const isAuthenticated = await authStore.checkAuth()
                if (!isAuthenticated) {
                    next('/login')
                    return
                }
            }
            
            // 检查角色权限
            if (to.meta.roles) {
                const hasRole = (to.meta.roles as string[]).some(role =>
                    authStore.hasRole(role)
                )
                if (!hasRole) {
                    next('/403') // 无权限页面
                    return
                }
            }
            
            // 检查具体权限
            if (to.meta.permissions) {
                const hasPermission = (to.meta.permissions as string[]).some(
                    permission => authStore.hasPermission(permission)
                )
                if (!hasPermission) {
                    next('/403')
                    return
                }
            }
        }
        
        next()
    })
}

3. 动态路由添加

// utils/permission.ts
import { asyncRoutes } from '@/router/routes'
import type { RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

export function filterAsyncRoutes(routes: RouteRecordRaw[]): RouteRecordRaw[] {
    const authStore = useAuthStore()
    
    return routes.filter(route => {
        if (route.meta?.roles) {
            const hasRole = (route.meta.roles as string[]).some(role =>
                authStore.hasRole(role)
            )
            if (!hasRole) return false
        }
        
        if (route.meta?.permissions) {
            const hasPermission = (route.meta.permissions as string[]).some(
                permission => authStore.hasPermission(permission)
            )
            if (!hasPermission) return false
        }
        
        if (route.children) {
            route.children = filterAsyncRoutes(route.children)
        }
        
        return true
    })
}

export function addRoutes(router: any) {
    const accessedRoutes = filterAsyncRoutes(asyncRoutes)
    accessedRoutes.forEach(route => {
        router.addRoute(route)
    })
}

🎨 组件级权限控制

1. 权限指令实现

// permissions/directives/permission.ts
import type { App } from 'vue'
import { useAuthStore } from '@/stores/auth'

export const permissionDirective = {
    mounted(el: HTMLElement, binding: any) {
        const { value } = binding
        const authStore = useAuthStore()
        
        if (value && value instanceof Array && value.length > 0) {
            const hasPermission = authStore.hasAnyPermission(value)
            
            if (!hasPermission) {
                el.parentNode?.removeChild(el)
            }
        } else {
            throw new Error('需要权限数组,例如 v-permission="[\'user:read\']"')
        }
    }
}

export function setupPermissionDirective(app: App) {
    app.directive('permission', permissionDirective)
}

2. 权限组件封装

<template>
    <div v-if="hasPermission">
        <slot />
    </div>
</template>

<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'

interface Props {
    permission?: string | string[]
    role?: string | string[]
    mode?: 'and' | 'or'
}

const props = withDefaults(defineProps<Props>(), {
    mode: 'and'
})

const authStore = useAuthStore()

const hasPermission = computed(() => {
    if (props.permission) {
        const permissions = Array.isArray(props.permission) 
            ? props.permission 
            : [props.permission]
        
        if (props.mode === 'and') {
            return permissions.every(p => authStore.hasPermission(p))
        } else {
            return permissions.some(p => authStore.hasPermission(p))
        }
    }
    
    if (props.role) {
        const roles = Array.isArray(props.role) ? props.role : [props.role]
        
        if (props.mode === 'and') {
            return roles.every(r => authStore.hasRole(r))
        } else {
            return roles.some(r => authStore.hasRole(r))
        }
    }
    
    return true
})
</script>

3. 使用示例

<template>
    <div>
        <!-- 指令方式 -->
        <button v-permission="['user:write']">编辑用户</button>
        
        <!-- 组件方式 -->
        <Permission :permission="['user:delete']">
            <button>删除用户</button>
        </Permission>
        
        <!-- 角色控制 -->
        <Permission role="admin">
            <button>管理员功能</button>
        </Permission>
    </div>
</template>

📊 权限管理界面

1. 角色权限管理组件

<template>
    <div class="role-management">
        <div class="header">
            <h2>角色权限管理</h2>
            <Permission :permission="['role:write']">
                <button @click="showCreateDialog">新增角色</button>
            </Permission>
        </div>
        
        <table>
            <thead>
                <tr>
                    <th>角色名称</th>
                    <th>描述</th>
                    <th>权限数量</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="role in roles" :key="role.id">
                    <td>{{ role.name }}</td>
                    <td>{{ role.description }}</td>
                    <td>{{ role.permissions.length }}</td>
                    <td>
                        <Permission :permission="['role:write']">
                            <button @click="editRole(role)">编辑</button>
                        </Permission>
                        <Permission :permission="['role:delete']">
                            <button @click="deleteRole(role)">删除</button>
                        </Permission>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'

interface Role {
    id: string
    name: string
    description: string
    permissions: string[]
}

const authStore = useAuthStore()
const roles = ref<Role[]>([])

onMounted(async () => {
    await loadRoles()
})

async function loadRoles() {
    // 调用API获取角色列表
    roles.value = await api.getRoles()
}
</script>

🔒 安全最佳实践

🛡️ 前端安全措施

  • JWT 安全存储 - httpOnly Cookie 或安全存储
  • Token 自动刷新 - 避免长时间过期
  • XSS 防护 - 输入输出转义
  • CSRF 防护 - Token 验证
  • 路由保护 - 动态路由加载

📝 权限设计原则

  • 最小权限原则 - 只授予必要权限
  • 职责分离 - 不同角色不同权限
  • 权限继承 - 合理的权限层级
  • 审计日志 - 记录关键操作
  • 定期审查 - 权限定期检查

权限验证流程

sequenceDiagram participant U as 用户 participant R as 路由 participant A as 认证Store participant S as 服务器 participant C as 组件 U->>R: 访问路由 R->>A: 检查认证状态 A->>S: 验证Token有效性 S-->>A: 返回用户信息 A->>R: 认证结果 R->>C: 渲染组件 C->>A: 检查组件权限 A-->>C: 权限验证结果 C->>U: 显示/隐藏内容

⚠️ 重要提醒

前端权限控制只是用户体验优化,真正的安全必须在后端实现!

所有接口都必须进行权限验证,防止直接API调用绕过前端控制。建议:

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流