RuoYi-Vue前端权限控制:路由守卫与按钮级权限

RuoYi-Vue前端权限控制:路由守卫与按钮级权限

【免费下载链接】RuoYi-Vue :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本 【免费下载链接】RuoYi-Vue 项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue

在后台管理系统开发中,权限控制是保障系统安全的核心环节。RuoYi-Vue作为典型的前后端分离权限管理系统,其前端权限控制采用了"路由守卫+动态路由+指令权限"的三层架构,实现了从页面访问到按钮操作的全链路权限管控。本文将深入解析这一架构的实现原理与实践方式。

权限控制整体架构

RuoYi-Vue的前端权限控制体系主要包含三个层级:

  • 路由级控制:通过路由守卫(Router Guard)实现页面访问权限控制
  • 菜单级控制:基于用户角色动态生成可访问菜单
  • 按钮级控制:通过自定义指令实现操作按钮的权限控制

权限控制架构

权限数据的流转路径如下:

  1. 用户登录后获取权限标识集合
  2. 基于权限动态生成路由配置
  3. 通过路由守卫控制页面访问权限
  4. 使用指令控制页面元素的显示权限

核心实现文件包括:

路由守卫:页面访问控制

路由守卫是前端权限控制的第一道防线,通过拦截路由跳转请求,验证用户是否具备访问权限。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()
    }
  }
})

关键实现步骤

  1. 登录状态验证:通过getToken()检查用户是否已登录
  2. 白名单处理:对登录页、注册页等公开页面直接放行
  3. 权限加载判断:检查是否已加载用户权限信息,未加载则触发加载
  4. 动态路由生成:调用GenerateRoutes action生成权限路由表
  5. 路由重定向:使用next({ ...to, replace: true })确保动态路由添加完成

路由守卫工作流程图

mermaid

动态路由:菜单权限控制

动态路由是根据用户权限动态生成可访问菜单的核心机制。RuoYi-Vue在ruoyi-ui/src/store/modules/permission.js中实现了这一功能。

路由生成流程

  1. 获取路由数据:通过getRouters()从后端API获取路由配置
  2. 路由转换:将后端返回的路由数据转换为前端可识别的路由配置
  3. 组件映射:将路由配置中的component字符串转换为实际组件
  4. 权限过滤:根据用户权限过滤生成最终路由表

核心实现代码

// 生成路由
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>

实现逻辑分析

  1. 权限标识验证:将指令绑定的值(权限标识数组)与用户拥有的权限进行比对
  2. 权限判断:如果用户拥有*:*:*超级权限或包含所需权限标识,则显示元素
  3. 元素移除:如果用户无权限,则从DOM中移除该元素

权限数据流转

RuoYi-Vue的权限数据从后端到前端的完整流转过程如下:

  1. 登录认证:用户提交账号密码,后端验证通过后返回Token和用户基本信息
  2. 权限加载:前端通过GetInfo action获取用户角色和权限标识集合
  3. 路由生成:调用GenerateRoutes action,基于权限标识动态生成路由配置
  4. 菜单渲染:根据生成的路由配置渲染侧边栏菜单
  5. 按钮控制:页面渲染时通过v-hasPermi指令控制按钮显示

权限数据流转

实践应用与扩展

自定义权限指令扩展

除了按钮级权限控制,还可以扩展实现其他类型的权限控制指令,如:

  • v-hasRole:基于角色的权限控制
  • v-hasAnyPermi:满足任一权限即可访问
  • v-hasAllPermi:需要满足所有权限才能访问

权限缓存策略

为提升用户体验,可优化权限数据的缓存策略:

  1. 将权限数据存储在localStoragesessionStorage
  2. 实现权限数据的过期机制,避免权限变更后需要强制刷新
  3. 在用户信息更新时同步更新权限缓存

总结与最佳实践

RuoYi-Vue的前端权限控制体系通过路由守卫、动态路由和指令权限三层架构,实现了从页面到按钮的全方位权限控制。在实际应用中,建议:

  1. 合理规划权限粒度:根据业务需求设计合适的权限粒度,避免过粗或过细
  2. 权限与角色分离:通过角色关联权限,简化权限管理
  3. 前端权限仅为辅助:始终保持后端权限验证,前端权限控制仅作为用户体验优化
  4. 权限变更即时生效:实现权限变更后的动态刷新,无需用户重新登录

通过这套权限控制体系,RuoYi-Vue能够满足大多数企业级后台系统的权限管理需求,为系统安全提供了坚实保障。

官方文档:doc/若依环境使用手册.docx 权限控制源码:ruoyi-ui/src/permission.js 指令权限实现:ruoyi-ui/src/directive/permission/hasPermi.js

【免费下载链接】RuoYi-Vue :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本 【免费下载链接】RuoYi-Vue 项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值