首页
/ RuoYi-Vue3权限系统设计:多租户权限隔离实现方案

RuoYi-Vue3权限系统设计:多租户权限隔离实现方案

2026-02-04 04:01:25作者:庞眉杨Will

一、权限系统痛点与挑战

你是否在多租户架构中遇到过以下问题?

  • 不同租户用户权限边界模糊导致数据越权访问
  • 租户定制化权限配置与系统通用性难以平衡
  • 权限变更时全局刷新导致用户体验下降
  • 复杂权限校验逻辑拖累系统性能

本文将基于RuoYi-Vue3框架,提供一套完整的多租户权限隔离解决方案,通过"数据隔离+权限控制+动态路由"三重防护机制,解决多租户场景下的权限管理难题。

读完本文你将获得:

  • 理解RuoYi-Vue3权限系统的底层实现原理
  • 掌握多租户权限隔离的设计方法论
  • 学会基于现有框架扩展租户隔离功能
  • 获取可直接复用的权限控制代码模块

二、RuoYi-Vue3权限系统基础架构

2.1 权限系统核心组件

RuoYi-Vue3权限系统采用"前后端分离"架构,主要由以下核心组件构成:

classDiagram
    class 权限存储模块 {
        +routes[] 路由列表
        +addRoutes[] 动态路由
        +sidebarRouters[] 侧边栏路由
        +generateRoutes() 生成路由
        +setRoutes() 设置路由
    }
    
    class 权限校验模块 {
        +checkPermi() 权限检查
        +checkRole() 角色检查
    }
    
    class 指令权限模块 {
        +v-hasPermi 权限指令
        +v-hasRole 角色指令
    }
    
    class API权限接口 {
        +listRole() 获取角色列表
        +getRole() 获取角色详情
        +dataScope() 数据权限配置
    }
    
    权限存储模块 --> 权限校验模块 : 提供权限数据
    权限校验模块 --> 指令权限模块 : 提供校验逻辑
    API权限接口 --> 权限存储模块 : 提供后端数据

2.2 权限流程时序图

权限校验的完整流程如下:

sequenceDiagram
    participant 用户
    participant 路由守卫
    participant 权限存储
    participant 后端API
    participant 视图渲染
    
    用户->>路由守卫: 访问页面
    路由守卫->>权限存储: 检查是否有权限
    权限存储-->>路由守卫: 返回权限检查结果
    alt 无权限
        路由守卫->>用户: 重定向到401/404
    else 有权限
        路由守卫->>后端API: 请求动态路由数据
        后端API-->>路由守卫: 返回路由数据
        路由守卫->>权限存储: 存储动态路由
        权限存储->>视图渲染: 提供渲染数据
        视图渲染->>用户: 展示页面
    end

2.3 核心权限数据结构

权限系统使用的数据结构定义如下:

// 路由数据结构
{
  path: '/system',                // 路由路径
  component: Layout,              // 组件
  redirect: '/system/user',       // 重定向
  name: 'System',                 // 路由名称
  meta: {
    title: '系统管理',             // 标题
    icon: 'system',               // 图标
    roles: ['admin'],             // 角色权限
    permissions: ['system:user:list'] // 操作权限
  },
  children: [                     // 子路由
    {
      path: 'user',
      name: 'User',
      component: () => import('@/views/system/user/index'),
      meta: { 
        title: '用户管理', 
        permissions: ['system:user:list'] 
      }
    }
  ]
}

三、权限系统实现原理深度剖析

3.1 权限存储模块实现

权限存储模块(permission.js)是权限系统的核心,负责存储和管理权限数据:

// src/store/modules/permission.js 核心代码
const usePermissionStore = defineStore(
  'permission',
  {
    state: () => ({
      routes: [],           // 完整路由列表
      addRoutes: [],        // 动态添加的路由
      sidebarRouters: []    // 侧边栏路由
    }),
    actions: {
      // 生成路由
      generateRoutes(roles) {
        return new Promise(resolve => {
          // 向后端请求路由数据
          getRouters().then(res => {
            const sdata = JSON.parse(JSON.stringify(res.data))
            const sidebarRoutes = filterAsyncRouter(sdata)
            this.setRoutes(sidebarRoutes)
            this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
            resolve(sidebarRoutes)
          })
        })
      },
      
      // 设置路由
      setRoutes(routes) {
        this.addRoutes = routes
        this.routes = constantRoutes.concat(routes)
      }
    }
  })

// 过滤路由,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
  return asyncRouterMap.filter(route => {
    if (route.component) {
      // Layout组件特殊处理
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else {
        // 动态导入组件
        route.component = loadView(route.component)
      }
    }
    // 递归处理子路由
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    }
    return true
  })
}

3.2 权限校验实现

权限校验模块提供了两种校验方式:函数式校验和指令式校验。

函数式校验(permission.js):

// src/utils/permission.js
export function checkPermi(value) {
  if (value && value instanceof Array && value.length > 0) {
    const permissions = useUserStore().permissions
    const all_permission = "*:*:*"
    
    // 检查是否拥有权限
    const hasPermission = permissions.some(permission => {
      return all_permission === permission || value.includes(permission)
    })
    
    return hasPermission
  }
  return false
}

export function checkRole(value) {
  if (value && value instanceof Array && value.length > 0) {
    const roles = useUserStore().roles
    const super_admin = "admin"
    
    // 检查是否拥有角色
    const hasRole = roles.some(role => {
      return super_admin === role || value.includes(role)
    })
    
    return hasRole
  }
  return false
}

指令式校验(hasPermi.js):

// src/directive/permission/hasPermi.js
export default {
  mounted(el, binding) {
    const { value } = binding
    const permissions = useUserStore().permissions
    const all_permission = "*:*:*"

    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissions.some(permission => {
        return all_permission === permission || value.includes(permission)
      })

      // 没有权限则移除元素
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值,如 v-hasPermi="['system:user:add']"`)
    }
  }
}

3.3 角色与权限API接口

角色管理API提供了角色和权限的CRUD操作:

// src/api/system/role.js 核心接口
// 查询角色列表
export function listRole(query) {
  return request({
    url: '/system/role/list',
    method: 'get',
    params: query
  })
}

// 查询角色详细
export function getRole(roleId) {
  return request({
    url: '/system/role/' + roleId,
    method: 'get'
  })
}

// 角色数据权限
export function dataScope(data) {
  return request({
    url: '/system/role/dataScope',
    method: 'put',
    data: data
  })
}

// 根据角色ID查询部门树结构
export function deptTreeSelect(roleId) {
  return request({
    url: '/system/role/deptTree/' + roleId,
    method: 'get'
  })
}

四、多租户权限隔离设计方案

4.1 多租户权限隔离模型

多租户权限隔离采用"租户-角色-用户-权限"四维模型:

mindmap
  root((多租户权限模型))
    租户层
      租户ID标识
      租户数据隔离
      租户配置独立
    角色层
      系统角色
      租户角色
      自定义角色
    用户层
      租户管理员
      租户普通用户
      跨租户用户
    权限层
      功能权限
      数据权限
      菜单权限

4.2 多租户权限隔离实现方案

基于RuoYi-Vue3现有架构,我们可以通过以下方式扩展多租户权限隔离功能:

方案一:基于数据权限的租户隔离

利用现有dataScope数据权限接口,扩展租户维度的数据隔离:

// 扩展数据权限接口,增加租户ID参数
export function dataScope(data) {
  // 添加租户ID到数据权限配置中
  data.tenantId = useUserStore().tenantId;
  return request({
    url: '/system/role/dataScope',
    method: 'put',
    data: data
  })
}

// 修改权限检查函数,增加租户维度检查
export function checkPermi(value) {
  // 原有权限检查逻辑...
  
  // 增加租户权限检查
  const tenantPermissions = useUserStore().tenantPermissions;
  const hasTenantPermission = tenantPermissions.some(permission => {
    return value.includes(permission);
  });
  
  return hasPermission && hasTenantPermission;
}

方案二:基于动态路由的租户隔离

修改动态路由生成逻辑,为路由添加租户前缀:

// 修改路由生成函数,增加租户前缀
function filterAsyncRouter(asyncRouterMap) {
  return asyncRouterMap.filter(route => {
    // 为路由路径添加租户前缀
    if (useUserStore().tenantId && !route.path.startsWith('/tenant/')) {
      route.path = `/tenant/${useUserStore().tenantId}${route.path}`;
    }
    
    // 递归处理子路由
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children);
    }
    
    return true;
  });
}

// 修改路由守卫,增加租户路由检查
router.beforeEach(async(to, from, next) => {
  // 原有路由守卫逻辑...
  
  // 检查租户路由权限
  if (to.path.startsWith('/tenant/') && !hasTenantPermission(to.path.split('/')[2])) {
    next('/403');
    return;
  }
  
  next();
});

方案三:基于租户上下文的权限隔离

实现租户上下文管理,在请求中自动附加租户信息:

// src/utils/request.js 增加租户ID拦截器
service.interceptors.request.use(config => {
  // 在请求头中添加租户ID
  if (useUserStore().tenantId) {
    config.headers['Tenant-Id'] = useUserStore().tenantId;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

// 扩展用户存储,增加租户信息
const useUserStore = defineStore(
  'user',
  {
    state: () => ({
      // 原有用户信息...
      tenantId: '',          // 租户ID
      tenantName: '',        // 租户名称
      tenantPermissions: [], // 租户级权限
      tenantRole: ''         // 租户角色
    }),
    // ...
  })

4.3 多租户权限控制矩阵

不同租户类型拥有的权限范围如下表所示:

权限类型 系统管理员 租户管理员 租户普通用户 跨租户用户
用户管理 全部权限 租户内权限 无权限 指定租户
角色管理 全部权限 租户内权限 无权限 无权限
菜单管理 全部权限 租户内权限 无权限 指定菜单
数据权限配置 全部权限 租户内权限 无权限 无权限
租户配置 全部权限 部分权限 无权限 无权限
跨租户数据访问 允许 禁止 禁止 部分允许

五、租户隔离实现步骤与代码示例

5.1 扩展用户存储模块

首先扩展用户存储模块,增加租户相关信息:

// src/store/modules/user.js 扩展
const useUserStore = defineStore(
  'user',
  {
    state: () => ({
      // 原有用户信息...
      tenantId: '',          // 租户ID
      tenantName: '',        // 租户名称
      tenantKey: '',         // 租户标识
      tenantPermissions: []  // 租户权限列表
    }),
    actions: {
      // 登录成功后获取租户信息
      async afterLoginAction(data) {
        // 原有登录逻辑...
        
        // 获取租户信息
        const tenantInfo = await this.getTenantInfo();
        this.tenantId = tenantInfo.tenantId;
        this.tenantName = tenantInfo.tenantName;
        this.tenantKey = tenantInfo.tenantKey;
        this.tenantPermissions = tenantInfo.permissions;
      },
      
      // 获取租户信息接口
      getTenantInfo() {
        return request({
          url: '/system/tenant/info',
          method: 'get'
        });
      }
    }
  })

5.2 修改权限指令与路由

修改权限指令和路由生成逻辑,增加租户维度检查:

// 修改v-hasPermi指令,增加租户权限检查
export default {
  mounted(el, binding) {
    const { value } = binding;
    const permissions = useUserStore().permissions;
    const tenantPermissions = useUserStore().tenantPermissions;
    const all_permission = "*:*:*";

    if (value && value instanceof Array && value.length > 0) {
      // 系统权限检查
      const hasPermission = permissions.some(permission => {
        return all_permission === permission || value.includes(permission);
      });
      
      // 租户权限检查
      const hasTenantPermission = tenantPermissions.some(permission => {
        return all_permission === permission || value.includes(permission);
      });
      
      // 双重权限检查
      if (!hasPermission || !hasTenantPermission) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    } else {
      throw new Error(`请设置操作权限标签值`);
    }
  }
}

5.3 实现租户切换功能

实现租户切换功能,切换时重新加载权限:

// src/views/system/user/profile/index.vue 增加租户切换
<template>
  <el-select 
    v-model="tenantId" 
    @change="handleTenantChange"
    placeholder="选择租户"
  >
    <el-option 
      v-for="tenant in tenantList" 
      :key="tenant.tenantId" 
      :label="tenant.tenantName" 
      :value="tenant.tenantId"
    ></el-option>
  </el-select>
</template>

<script setup>
import { ref } from 'vue';
import useUserStore from '@/store/modules/user';
import usePermissionStore from '@/store/modules/permission';

const userStore = useUserStore();
const permissionStore = usePermissionStore();
const tenantId = ref(userStore.tenantId);
const tenantList = ref([]);

// 获取租户列表
const getTenantList = () => {
  request({
    url: '/system/tenant/list',
    method: 'get'
  }).then(res => {
    tenantList.value = res.data;
  });
};

// 切换租户处理
const handleTenantChange = async (val) => {
  // 保存当前租户ID
  userStore.setTenantId(val);
  
  // 重新加载权限
  await permissionStore.generateRoutes();
  
  // 刷新当前页面
  window.location.reload();
};

// 初始化
getTenantList();
</script>

六、总结与最佳实践

6.1 多租户权限隔离方案对比

隔离方案 实现复杂度 隔离强度 性能影响 适用场景
数据权限隔离 简单租户隔离需求
动态路由隔离 中等复杂度租户系统
租户上下文隔离 最高 复杂多租户企业级系统

6.2 权限系统性能优化建议

  1. 权限数据缓存:将权限数据缓存到本地,减少重复请求
// 权限数据缓存示例
export function getPermissionCache() {
  const cacheKey = `permissions_${useUserStore().userId}_${useUserStore().tenantId}`;
  const cacheData = localStorage.getItem(cacheKey);
  
  if (cacheData) {
    return JSON.parse(cacheData);
  }
  
  // 无缓存则请求并缓存
  return request({
    url: '/system/permission/getUserPermission',
    method: 'get'
  }).then(res => {
    localStorage.setItem(cacheKey, JSON.stringify(res.data));
    return res.data;
  });
}
  1. 路由懒加载优化:采用组件懒加载,减少初始加载时间
  2. 权限指令优化:批量处理权限指令,减少DOM操作
  3. 权限检查防抖:避免频繁的权限检查操作

6.3 安全加固建议

  1. 前后端双重校验:前端权限控制仅为辅助,后端必须实现严格的权限校验
  2. 敏感操作日志:记录权限变更、租户切换等敏感操作
  3. 定期权限审计:定期检查权限配置,清理不合理权限
  4. 最小权限原则:为用户分配最小必要权限

6.4 后续扩展方向

  1. 细粒度API权限控制:为每个API接口配置独立的权限控制
  2. 基于RBACv3的权限模型:实现更复杂的角色继承和权限约束
  3. 权限申请与审批流程:实现权限的申请、审批、变更流程化管理
  4. 权限使用统计分析:分析权限使用情况,优化权限配置

七、学习资源与参考资料

  1. RuoYi-Vue3官方文档:权限管理部分
  2. 《多租户架构设计与实现》- Martin Fowler
  3. 《RBAC权限模型详解》- NIST
  4. 《前后端分离架构下的权限系统设计》

如果觉得本文对你有帮助,请点赞、收藏、关注三连支持!

下期预告:《RuoYi-Vue3微前端改造实战:基于qiankun的多应用集成方案》

通过本文介绍的方案,我们可以在RuoYi-Vue3现有权限系统基础上,以最小的改造成本实现多租户权限隔离。无论是SaaS平台还是企业级多租户系统,都可以借鉴这种设计思路,构建安全、灵活、高性能的权限管理系统。

登录后查看全文
热门项目推荐
相关项目推荐