系统权限-数据权限案例分析

2023-10-31

前言

传统管理系统权限通常分为功能型权限和数据权限。
**功能型权限**:通常基于RBAC模式进行设计。RBAC(Role-Based Access Control)基于角色的访问控制。
相当成熟的权限模型。三个要素:用户、角色、权限。用户与角色是多对多关系,角色与权限是多对多关系。
关键元素:
	用户(员工):成功认证并登录系统的操作员(主体:who);
	权限:访问资源的许可(how);
	角色:权限的集合体;
	业务系统:每个独立业务系统,包括各业务系统中的菜单、操作按钮等(what)
通过配置用户的角色,角色关联权限(即菜单、操作按钮等资源),实现用户对资源的访问。

**数据权限:**是指对系统用户进行数据资源可见性的控制,通俗的解释就是:符合某条件的用户只能看到该条件
下对应的数据资源。简单的数据权限就是:用户只能看到自己的数据。
如:领导需要看到所有下属员工的客户数据,员工只能看自己的客户数据;
       角色A能看到全国的产品数据,角色B只能看到上海的产品数据;

在这里插入图片描述
功能权限和数据权限是相互独立.
ACL:访问控制列表( ACL)是附加到对象的权限列表,ACL指定哪些身份被授予对给定对象的哪些操作。在单个域对象上定义特定用户/角色的权限——而不是在典型的每个操作级别上全面定义权限。Spring Security访问控制列表 是一个支持域对象安全的Spring组件。
Spring Security ACL 核心概念和组件
Spring Security ACL 简介
Spring Security ACL 简介

一、数据权限

本文章只分析 基于部门资源下数据权限的设计与实现。设计与代码为:若依ruoyi开源架构

三、源代码下载

若依源代码

四、数据库权限设计图

系统权限、自定义角色-部门权限、部门数据权限
1.若依ruoyi数据表间的关联没有创建外键,表与表间的关联性通过业务代码维护。
2.通过图上可知,数据权限分 自定义角色-部门权限、部门数据权限两种:
1) 自定义角色-部门权限:用户-----系统角色部门(sys-role-dept);
2) 部门数据权限:用户-----系统部门(sys-dept);

#sys_role
CREATE TABLE `sys_role` (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称',
  `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:本人数据权限)',
 ......
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色信息表';

#sys_dept
CREATE TABLE `sys_dept` (
  `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id',
  `parent_id` bigint DEFAULT '0' COMMENT '父部门id',
  `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '祖级列表',
  `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '部门名称',
........
  PRIMARY KEY (`dept_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=232 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部门表';

data_scope:数据范围 1:全部数据权限[即按菜单权限进行资源的过滤]
2:自定数据权限 [可选择指定特殊或其它多部门,进行跨部门数据权限控制,即:
自定义角色-部门权限]
3:本部门数据权限 [用于 方式二:部门数据权限 ]
4:本部门及以下数据权限[sys_dept表ancestors字段存储部门上、下级关联,用于
方式二:部门数据权限]
5:本人数据权限

五、数据权限前台界面

1-自定义角色-部门权限
实现:自定义角色-部门权限:所选多个部门与 某个角色关联,存储于角色-部门中间表sys-role-dept,通过用户的角色查询所有的部门,再进行用户部门的数据过滤。
2-部门数据权限:用户-系统部门
实现:不涉及 角色-部门中间表sys-role-dept,通过用户、部门以及部门表ancestors字段(存储部门上、下级关系)进行过滤

六、数据权限服务端

6.1 aop 拦截“数据范围”

/**
 * 1,定义 数据权限过滤注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
    /**
     * 部门表的别名
     */
    public String deptAlias() default "";

    /**
     * 用户表的别名
     */
    public String userAlias() default "";
}


/**
 * 2.数据过滤处理类DataScopeAspect:aop切面方式
 */
@Aspect
@Component
public class DataScopeAspect
{
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    /**
     * 对带有注解DataScope的类方法在执行前进行拦截
     */
    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }
     /**
     * 拦截时执行的数据过滤处理
     */
    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
    {
        // 获取当前的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNotNull(loginUser))
        {
            SysUser currentUser = loginUser.getUser();
            // 如果是超级管理员,则不过滤数据
            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
            {
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤核心处理逻辑
     * @param joinPoint 切点
     * @param user 用户
     * @param deptAlias 部门别名
     * @param userAlias 用户别名
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
    {
        StringBuilder sqlString = new StringBuilder();

        for (SysRole role : user.getRoles())
        {
            String dataScope = role.getDataScope();
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getRoleId()));
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                /*ancestors:存储sys_dept祖先/上级 部门列表dept_id,如:ancestors=0,100,101*/
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            }
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StringUtils.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(" OR 1=0 ");
                }
            }
        }

        if (StringUtils.isNotBlank(sqlString.toString()))
        {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
               //向 每个资源对象(如SysUser:必须继承BaseEntity基础实体类)注入请求参数:private Map<String, Object> params;
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }
   ......
}

每个资源对象,如SysUser必须继承BaseEntity基础实体类,如:

//BaseEntity 
/**
 * Entity基类
 */
public class BaseEntity implements Serializable
{
    private static final long serialVersionUID = 1L;

    .......

    /** 请求参数 */
    private Map<String, Object> params;
}

//SysUser
/**
 * 用户对象 sys_user:继承BaseEntity基础实体类
 */
public class SysUser extends BaseEntity
.......
}

6.2 数据实现层ServiceImpl 埋点

    /**
     * 根据条件分页查询用户列表
     * @DataScope(deptAlias = "d", userAlias = "u") 数据范围 DataScope切点 进行 埋点,被 数据过滤处理类DataScopeAspect 拦截
     * @param user 用户信息
     * @return 用户信息集合信息
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<SysUser> selectUserList(SysUser user)
    {
        return userMapper.selectUserList(user);
    }

mybitais 数据层SysUserMapper.xml查询sql:

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
		select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader
		from sys_user u
		left join sys_dept d on u.dept_id = d.dept_id
		where u.del_flag = '0'
		<if test="userId != null and userId != 0">
			AND u.user_id = #{userId}
		</if>
		<if test="userName != null and userName != ''">
			AND u.user_name like concat('%', #{userName}, '%')
		</if>
		<if test="status != null and status != ''">
			AND u.status = #{status}
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
		</if>
		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
		</if>
		<if test="deptId != null and deptId != 0">
			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>

说明:数据范围过滤 通过BaseEntity类 ${params.dataScope}

七、总结

7.1设计思路

1.在需要数据范围类实现方法中埋入切点:@DataScope(deptAlias = “d”, userAlias = “u”)
2.数据过滤处理类DataScopeAspect 切面方式拦截并根据用户数据范围方式
[1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数
据权限 5:本人数据权限] 进行分类处理
3.生成预查询sql片断,并回写到资源对象请求参数Map<String, Object> params中
4.执行查询时,mybitais 资源数据层XxxxxMapper.xml预定义对象请求参数params

7.2 缺陷

mybitais 资源数据层XxxxxMapper.xml sql需要并联部门(sys-dept)表
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

系统权限-数据权限案例分析 的相关文章

随机推荐

  • 算法实践1_线性回归

    参数解释 sklearn linear model LinearRegression fit intercept True normalize False copy X True n jobs None 超参 解释 类型 默认值 fit i
  • 生产API版本及SIMNOW环境说明

  • Java 读取某文件下的所有文件名称以及大小,并输出在xls表格里

    方法一 package com test demo test import lombok Data import java io import java util ArrayList import java util List Title
  • np.clip的使用方法

    np clip的使用方法 参数数量及其作用 示例 参数数量及其作用 np clip是一个截取函数 用于截取数组中小于或者大于某值的部分 并使得被截取部分等于固定值 函数如下 np clip a a min a max out None 该函
  • WSL2中使用GPU

    在WSL2上安装CUDA和NVIDIA HPC SDK 1 WSL2和Ubuntu的安装 2 安装显卡驱动 3 在WSL2中安装CUDA 4 安装 NVIDIA HPC SDK Windows10内部预览版20145及之后的版本的WSL2支
  • 软件设计模式详解 #CSDN博文精选# #IT技术# #软件模式# #设计模式#

    大家好 小C将继续与你们见面 带来精选的CSDN博文 又到周一啦 上周的系统化学习专栏已经结束 我们总共一起学习了20篇文章 这周将开启全新专栏 放假不停学 全栈工程师养成记 在这里 你将收获 将系统化学习理论运用于实践 系统学习IT技术
  • 五、IDEA中创建Web项目

    文章目录 5 1 创建Web项目 5 1 1 创建项目 5 1 2 编写Servlet类 5 2 手动部署项目 5 3 自动部署项目 5 3 1 IDEA集成Tomcat 5 3 2 IDEA部署JavaWeb项目 5 4 war包部署 5
  • css被点击后改变样式,Js 通过点击改变css样式

    通过js 点击按钮去改变目标原始的背景颜色Change html function test4 event if event value 11 取div1 var div1 document getElementById div1 div1
  • voronoi图编程构造_可视化编程真的有那么糟糕?

    作者 Anton Livaja 译者 弯月 责编 屠敏 以下为译文 我想告诉你 如果使用恰当 可视化编程和是图解推理是一个非常强大的工具集 也就是说 只有当可视化编程扎根于数学和计算机科学并建立坚实的基础 才能发挥良好的作用 为了降低编程的
  • 《职场情绪稳定:内在的力量与策略》

    近期发生的新闻热点 如大规模裁员 创业公司倒闭 公共卫生事件等 让公众更加关注稳定情绪和心理健康的问题 在职场中 我们常常遇到各种挑战和压力 如何保持稳定的情绪成了一个重要的话题 首先 让我们分享一些工作中可能引发我们情绪波动的事情 我曾经
  • IT项目管理七

    Tony Prince 和他的团队正在做一个娱乐和健康方面的项目 他们被要求修改现有的成本估计 以便能有一个可靠的评价项目绩效的基线 你的进度和成本目标是在6个月内在200 000美元的预算下完成项目 1 作业一 准备和打印一页类似于图7
  • 求n个数的最小公倍数(C语言)

    Problem Description 求n个数的最小公倍数 Input 输入包含多个测试实例 每个测试实例的开始是一个正整数n 然后是n个正整数 Output 为每组测试数据输出它们的最小公倍数 每个测试实例的输出占一行 你可以假设最后的
  • java项目 畅购商城 购物车

    第10章 购物车 学习目标 能够通过SpringSecurity进行权限控制 掌握购物车流程 掌握购物车渲染 微服务之间的认证访问 1 SpringSecurity权限控制 用户每次访问微服务的时候 先去oauth2 0服务登录 登录后再访
  • 网易游戏(互娱)游戏研发一面&二面(已收到offer)

    简单来讲下上周面网易互娱的心得 因为我不是走内推而是直接怼笔试的 所以上周才有了笔试结果然后被告知面试 我面的岗位是游戏研发工程师 初级 一面 40分钟左右 开始是简单的自我介绍 C 关于C 问的比较简单 因为我跟面试官说我主要学的是Jav
  • 风格回调函数 vs c++风格虚基类

    http www cnblogs com raymon archive 2012 08 28 2660876 html 风格回调函数 vs c 风格虚基类 关于接口定义和调用的对比 c 中也很常用回调函数 比如MFC中 既可以用回调函数的方
  • APP移动端自动化基础及appium环境搭建

    目录 APP移动端自动化测试基础 主流移动端自动化工具 Appium介绍 Appium工作原理 Appium环境搭建 安装前准备工具 安装Android SDK 配置环境变量 安装Python client 安装夜神模拟器 mumu模拟器
  • 一文一图搞懂OSI七层模型

    什么是OSI 所谓的OSI 是由国际化标准组织 ISO 针对开放式网路架构所制定的电脑互连标准 全名是开放式通讯系统互连参考模型 Open System Interconnection Reference Model 简称OSI模型 该模型
  • Air780E

    目录 Air780E编译指南 准备工作 下载源码 注意 需要两个库 准备工具 工具链下载 开始编译 常见编译问题 Air780E编译指南 https wiki luatos com develop compile Air780E html
  • 全面深入彻底理解Python切片操作【原创】

    全面深入彻底理解Python切片操作 原创 我们基本上都知道Python的序列对象都是可以用索引号来引用的元素的 索引号可以是正数由0开始从左向右 也可以是负数由 1开始从右向左 在Python中对于具有序列结构的数据来说都可以使用切片操作
  • 系统权限-数据权限案例分析

    文章目录 前言 一 数据权限 三 源代码下载 四 数据库权限设计图 五 数据权限前台界面 六 数据权限服务端 6 1 aop 拦截 数据范围 6 2 数据实现层ServiceImpl 埋点 七 总结 7 1设计思路 7 2 缺陷 前言 传统