RuoYi-Vue前端权限控制:路由守卫与按钮级权限
在后台管理系统开发中,权限控制是保障系统安全的核心环节。RuoYi-Vue作为典型的前后端分离权限管理系统,其前端权限控制采用了"路由守卫+动态路由+指令权限"的三层架构,实现了从页面访问到按钮操作的全链路权限管控。本文将深入解析这一架构的实现原理与实践方式。
权限控制整体架构
RuoYi-Vue的前端权限控制体系主要包含三个层级:
- 路由级控制:通过路由守卫(Router Guard)实现页面访问权限控制
- 菜单级控制:基于用户角色动态生成可访问菜单
- 按钮级控制:通过自定义指令实现操作按钮的权限控制
权限数据的流转路径如下:
- 用户登录后获取权限标识集合
- 基于权限动态生成路由配置
- 通过路由守卫控制页面访问权限
- 使用指令控制页面元素的显示权限
核心实现文件包括:
- 路由守卫逻辑:ruoyi-ui/src/permission.js
- 权限状态管理:ruoyi-ui/src/store/modules/permission.js
- 按钮权限指令:ruoyi-ui/src/directive/permission/hasPermi.js
- 菜单API服务:ruoyi-ui/src/api/system/menu.js
路由守卫:页面访问控制
路由守卫是前端权限控制的第一道防线,通过拦截路由跳转请求,验证用户是否具备访问权限。RuoYi-Vue在ruoyi-ui/src/permission.js中实现了全局前置守卫。
核心实现逻辑
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else if (isWhiteList(to.path)) {
next()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (isWhiteList(to.path)) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
关键实现步骤
- 登录状态验证:通过
getToken()检查用户是否已登录 - 白名单处理:对登录页、注册页等公开页面直接放行
- 权限加载判断:检查是否已加载用户权限信息,未加载则触发加载
- 动态路由生成:调用
GenerateRoutesaction生成权限路由表 - 路由重定向:使用
next({ ...to, replace: true })确保动态路由添加完成
路由守卫工作流程图
动态路由:菜单权限控制
动态路由是根据用户权限动态生成可访问菜单的核心机制。RuoYi-Vue在ruoyi-ui/src/store/modules/permission.js中实现了这一功能。
路由生成流程
- 获取路由数据:通过
getRouters()从后端API获取路由配置 - 路由转换:将后端返回的路由数据转换为前端可识别的路由配置
- 组件映射:将路由配置中的component字符串转换为实际组件
- 权限过滤:根据用户权限过滤生成最终路由表
核心实现代码
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes)
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
})
}
路由组件映射
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type)
} else {
delete route['children']
delete route['redirect']
}
return true
})
}
按钮级权限:指令式控制
按钮级权限控制是细粒度权限控制的关键,RuoYi-Vue通过自定义指令v-hasPermi实现了页面元素的权限控制,定义在ruoyi-ui/src/directive/permission/hasPermi.js中。
指令实现原理
export default {
inserted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*"
const permissions = store.getters && store.getters.permissions
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value
const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission)
})
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置操作权限标签值`)
}
}
}
使用方式
在页面组件中,通过v-hasPermi指令控制按钮显示:
<el-button
v-hasPermi="['system:user:add']"
type="primary"
@click="handleAdd"
>
<i class="el-icon-plus"></i> 新增
</el-button>
实现逻辑分析
- 权限标识验证:将指令绑定的值(权限标识数组)与用户拥有的权限进行比对
- 权限判断:如果用户拥有
*:*:*超级权限或包含所需权限标识,则显示元素 - 元素移除:如果用户无权限,则从DOM中移除该元素
权限数据流转
RuoYi-Vue的权限数据从后端到前端的完整流转过程如下:
- 登录认证:用户提交账号密码,后端验证通过后返回Token和用户基本信息
- 权限加载:前端通过
GetInfoaction获取用户角色和权限标识集合 - 路由生成:调用
GenerateRoutesaction,基于权限标识动态生成路由配置 - 菜单渲染:根据生成的路由配置渲染侧边栏菜单
- 按钮控制:页面渲染时通过
v-hasPermi指令控制按钮显示
实践应用与扩展
自定义权限指令扩展
除了按钮级权限控制,还可以扩展实现其他类型的权限控制指令,如:
v-hasRole:基于角色的权限控制v-hasAnyPermi:满足任一权限即可访问v-hasAllPermi:需要满足所有权限才能访问
权限缓存策略
为提升用户体验,可优化权限数据的缓存策略:
- 将权限数据存储在
localStorage或sessionStorage中 - 实现权限数据的过期机制,避免权限变更后需要强制刷新
- 在用户信息更新时同步更新权限缓存
总结与最佳实践
RuoYi-Vue的前端权限控制体系通过路由守卫、动态路由和指令权限三层架构,实现了从页面到按钮的全方位权限控制。在实际应用中,建议:
- 合理规划权限粒度:根据业务需求设计合适的权限粒度,避免过粗或过细
- 权限与角色分离:通过角色关联权限,简化权限管理
- 前端权限仅为辅助:始终保持后端权限验证,前端权限控制仅作为用户体验优化
- 权限变更即时生效:实现权限变更后的动态刷新,无需用户重新登录
通过这套权限控制体系,RuoYi-Vue能够满足大多数企业级后台系统的权限管理需求,为系统安全提供了坚实保障。
官方文档:doc/若依环境使用手册.docx 权限控制源码:ruoyi-ui/src/permission.js 指令权限实现:ruoyi-ui/src/directive/permission/hasPermi.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





