1. 权限系统核心架构解析
现代前端权限系统的设计需要兼顾安全性和用户体验,Vue3生态下的解决方案通常采用Pinia状态管理+路由守卫的架构模式。这套方案的核心在于实现以下四个关键能力:
- 用户登录态持久化维护
- 动态权限数据与前端路由的映射
- 接口请求的权限校验
- 页面级访问控制
我曾在多个中后台项目中实践过这种架构,发现采用Pinia替代传统Vuex可以带来更好的TypeScript支持和模块化体验。下面通过一个电商后台的案例,详细拆解实现过程。
2. 技术栈选型与初始化
2.1 基础环境搭建
bash复制npm create vue@latest vue-auth-demo
cd vue-auth-demo
npm install pinia vue-router axios
建议使用Volar插件获得更好的类型提示。项目结构建议如下:
code复制/src
/stores
auth.store.ts
/router
index.ts
/guards
permission.ts
/api
auth.ts
2.2 Pinia存储设计
在auth.store.ts中定义核心状态:
typescript复制interface UserInfo {
userId: string
roles: string[]
permissions: string[]
}
export const useAuthStore = defineStore('auth', {
state: (): {
token: string | null
userInfo: UserInfo | null
routes: RouteRecordRaw[]
} => ({
token: localStorage.getItem('token'),
userInfo: null,
routes: []
}),
actions: {
async login(credentials: {username: string, password: string}) {
const { data } = await authApi.login(credentials)
this.token = data.token
this.userInfo = data.user
localStorage.setItem('token', data.token)
},
async fetchPermissions() {
if (!this.token) return
const { data } = await authApi.getPermissions()
this.userInfo!.permissions = data.permissions
this.generateRoutes(data.menus)
}
}
})
3. 登录流程深度实现
3.1 登录接口封装
在api/auth.ts中封装认证相关接口:
typescript复制import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 5000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export const authApi = {
login: (data: {username: string, password: string}) =>
service.post('/auth/login', data),
getPermissions: () =>
service.get('/auth/permissions'),
logout: () =>
service.post('/auth/logout')
}
3.2 登录页面实现关键点
vue复制<script setup>
const authStore = useAuthStore()
const router = useRouter()
const form = reactive({
username: '',
password: ''
})
const handleLogin = async () => {
try {
await authStore.login(form)
await authStore.fetchPermissions()
router.push('/dashboard')
} catch (err) {
// 错误处理逻辑
}
}
</script>
重要提示:密码字段必须使用type="password"确保输入安全,生产环境建议增加验证码校验
4. 动态路由与权限控制
4.1 路由守卫实现
在router/guards/permission.ts中:
typescript复制export function setupPermissionGuard(router: Router) {
router.beforeEach(async (to) => {
const authStore = useAuthStore()
// 白名单直接放行
if (whiteList.includes(to.path)) return true
// 检查token
if (!authStore.token) {
return `/login?redirect=${to.path}`
}
// 已登录但未获取权限信息
if (!authStore.userInfo?.permissions) {
try {
await authStore.fetchPermissions()
// 动态添加路由
authStore.routes.forEach(route => {
!router.hasRoute(route.name!) && router.addRoute(route)
})
return { ...to, replace: true }
} catch (err) {
authStore.logout()
return '/login'
}
}
// 检查路由权限
if (to.meta.roles && !authStore.userInfo.roles.some(r => to.meta.roles.includes(r))) {
return '/403'
}
})
}
4.2 动态路由生成算法
typescript复制function generateRoutes(menuData: MenuItem[]): RouteRecordRaw[] {
return menuData.map(menu => {
const route: RouteRecordRaw = {
path: menu.path,
name: menu.name,
component: () => import(`../views/${menu.component}.vue`),
meta: {
title: menu.title,
icon: menu.icon,
roles: menu.roles
}
}
if (menu.children) {
route.children = generateRoutes(menu.children)
}
return route
})
}
5. 权限刷新与状态保持
5.1 Token刷新机制
在axios响应拦截器中实现:
typescript复制service.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401 && !error.config._retry) {
error.config._retry = true
try {
const { data } = await authApi.refreshToken()
localStorage.setItem('token', data.token)
error.config.headers.Authorization = `Bearer ${data.token}`
return service(error.config)
} catch (e) {
authStore.logout()
window.location.reload()
}
}
return Promise.reject(error)
}
)
5.2 页面权限指令
实现v-permission指令:
typescript复制app.directive('permission', {
mounted(el, binding) {
const authStore = useAuthStore()
if (!authStore.userInfo?.permissions.includes(binding.value)) {
el.parentNode?.removeChild(el)
}
}
})
使用示例:
html复制<button v-permission="'user:create'">创建用户</button>
6. 生产环境优化实践
6.1 安全加固措施
- 接口防重放攻击:添加nonce和timestamp校验
- 敏感操作二次验证:关键操作需短信/邮箱确认
- JWT安全配置:
typescript复制// token过期时间设置为2-4小时 const tokenExpire = 2 * 60 * 60 // refreshToken设置为7天 const refreshTokenExpire = 7 * 24 * 60 * 60
6.2 性能优化方案
- 路由懒加载:使用import()动态加载组件
- 权限数据缓存:localStorage存储权限数据时设置版本号
- 接口合并:将权限接口与用户信息接口合并减少请求
7. 常见问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 登录后跳转404 | 动态路由未正确加载 | 检查router.addRoute调用时机 |
| 权限变更后未生效 | 缓存未清除 | 在fetchPermissions中清除旧路由 |
| 接口403错误 | token过期或权限不足 | 检查响应拦截器的刷新逻辑 |
| 页面按钮权限异常 | 指令绑定值错误 | 确认v-permission值与后端一致 |
我在实际项目中遇到过动态路由重复添加的问题,最终通过以下方式解决:
typescript复制// 在addRoute前先移除可能存在的旧路由
router.getRoutes().forEach(route => {
if (route.name && !initialRoutes.some(r => r.name === route.name)) {
router.removeRoute(route.name)
}
})
8. 高级扩展方向
- 数据权限控制:根据用户角色过滤接口返回数据
- 操作日志记录:关键操作自动记录审计日志
- 多租户支持:在路由meta中添加tenantId标识
- 微前端集成:将权限系统作为主应用的基础服务
这套方案在用户量超过50万的供应链系统中运行稳定,权限变更响应时间控制在200ms内。对于更复杂的场景,可以考虑引入RBAC0模型进行权限建模。