Springboot管理系统数据权限过滤(二)——SQL拦截器

上一节Springboot管理系统数据权限过滤——ruoyi实现方案对数据权限实现方案有了认识,本文将进一步优化权限过滤方案,实现对业务代码零入侵。

回顾上一章中权限方案:

  • 主要是通过注解拦截,拼接好权限脚本后,放到对象变量里面,然后在SQL中拼接该变量;使业务代码被入侵了。

为了实现对业务零入侵,实则是在SQL编写的时候,希望通过框架实现权限脚本的自动拼接,而非人为添加。
本文权限控制需要达到的效果:

  • 1.还是对组织进行权限控制;
  • 2.去掉编写sql时拼接权限过滤参数;使权限代码0侵入;

步骤:

1. 搭建springboot框架,完成mybatisplus集成和swagger集成

pom.xml文件


<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 方便等会写单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 实现对数据库连接池的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <!-- 本示例,我们使用 MySQL --><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency><!-- 实现对 MyBatis 的自动化配置 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- 引入 Swagger 依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- 引入 Swagger UI 依赖,以实现 API 接口的 UI 界面 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency></dependencies>

application.yaml

spring:# datasource 数据源配置内容datasource:url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: 123456# mybatis-plus 配置内容
mybatis-plus:configuration:map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。global-config:db-config:id-type: auto # ID 主键自增logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)mapper-locations: classpath*:mapper/*.xmltype-aliases-package: com.luo.chengrui.labs.lab02.dataobject # 配置数据库实体包路径# logging
logging:level:# dao 开启 debug 模式 mybatis 输入 sqlcom:luo:chengrui:labs: debug

UserDao.java

package com.luo.chengrui.labs.lab02.dataobject;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;import java.util.Date;/*** @author* @version 1.0.0* @description* @createTime 2023/07/20*/
@Data
@Accessors(chain = true)
@TableName(value = "users")
public class UserDO {/*** 用户编号*/private Long id;/*** 账号*/private String username;/*** 密码(明文)* <p>* ps:生产环境下,千万不要明文噢*/private String password;/*** 创建时间*/private Date createTime;
}

UserMapper.java

package com.luo.chengrui.labs.lab02.mapper;import com.luo.chengrui.labs.lab02.dataobject.UserDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface UserMapper {UserDO selectById(@Param("id") Integer id);List<UserDO> selectList();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.luo.chengrui.labs.lab02.mapper.UserMapper"><sql id="FIELDS">id, username</sql><select id="selectById" parameterType="Integer" resultType="UserDO">SELECT<include refid="FIELDS"/>FROM usersWHERE id = #{id}</select><select id="selectList" resultType="UserDo">SELECT<include refid="FIELDS"/>FROM users</select></mapper>

UserService.java

package com.luo.chengrui.labs.lab02.service;import com.luo.chengrui.labs.lab02.annotation.DataScope;
import com.luo.chengrui.labs.lab02.dataobject.UserDO;
import com.luo.chengrui.labs.lab02.mapper.UserMapper;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author* @version 1.0.0* @description* @createTime 2023/07/21*/
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;private UserService self() {return (UserService) AopContext.currentProxy();}/*** 方法未使用 @Transactional 注解,不会开启事务。* 对于 OrderMapper 和 UserMapper 的查询操作,分别使用其接口上的 @DS 注解,找到对应的数据源,执行操作。* 这样一看,在未开启事务的情况下,我们已经能够自由的使用多数据源落。*/public void method() {// 查询订单UserDO user = userMapper.selectById(1);System.out.println(user);}@DataScopepublic void method01() {// 查询订单UserDO user = userMapper.selectById(1);System.out.println(user);}@DataScopepublic List<UserDO> selectList() {return userMapper.selectList();}
}

UserController.java

package com.luo.chengrui.labs.lab02.controller;import com.luo.chengrui.labs.lab02.dataobject.UserDO;
import com.luo.chengrui.labs.lab02.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author* @version 1.0.0* @description* @createTime 2023/07/17*/
@RestController
@RequestMapping("/users")
@Api(tags = "用户 API 接口")
public class UserController {@AutowiredUserService userService;@GetMapping("/list")@ApiOperation(value = "查询用户列表", notes = "目前仅仅是作为测试,所以返回用户全列表")public List<UserDO> list() {// 查询列表List<UserDO> result = userService.selectList();// 返回列表return result;}}

SwaggerConfiguration.java

package com.luo.chengrui.labs.lab02.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** 访问地址:/swagger-ui.html* @author* @version 1.0.0* @description* @createTime 2023/07/17*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {@Beanpublic Docket createRestApi() {// 创建 Docket 对象return new Docket(DocumentationType.SWAGGER_2) // 文档类型,使用 Swagger2.apiInfo(this.apiInfo()) // 设置 API 信息// 扫描 Controller 包路径,获得 API 接口.select().apis(RequestHandlerSelectors.basePackage("com.luo.chengrui.labs.lab02.controller")).paths(PathSelectors.any())// 构建出 Docket 对象.build();}/*** 创建 API 信息*/private ApiInfo apiInfo() {return new ApiInfoBuilder().title("测试接口文档示例").description("我是一段描述").version("1.0.0") // 版本号.contact(new Contact("chengrui", "http://www.chengrui.cn", "chengrui@gmail.com")) // 联系人.build();}
}

Lab0201Application.java

package com.luo.chengrui.labs.lab02;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author* @version 1.0.0* @description* @createTime 2023/07/21*/
@SpringBootApplication
@MapperScan(basePackages = "com.luo.chengrui.labs.lab02.mapper")
public class Lab0201Application {public static void main(String[] args) {SpringApplication.run(Lab0201Application.class, args);}
}

到此完成框架搭建,访问:http://localhost:8080/swagger-ui.html,可看到以下页面即为成功。
在这里插入图片描述

2. 配置sql拦截器

创建 MybatisDatabaseInterceptor .java 类,类中大部分代码是对sql的解析,对表名的解析,对where语句的解析,真正需要关注的逻辑只是少部分,本部分代码里暂未添加对权限的控制,在下一文章中添加。
该类继承JsqlParserSupport ,同时实现InnerInterceptor接口

  • JsqlParserSupport 用于解析sql语句,可以对sql进行改造;方法有:processSelect、processUpdate、processDelete等;
  • InnerInterceptor 在执行sql语句之前的拦截器,方法有:beforeQuery、beforeUpdate、beforePrepare等,执行这些方法中可调用JsqlParserSupport 类中的方法对sql进行解析。
    通过实现以上5个方法即可对sql进行改造。
package com.luo.chengrui.labs.lab02.datapermission;import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.sql.Connection;/*** SQL拦截器,* 主要的 SQL 重写方法,* 主要是在执行SQL前拦截器,在执行之前可重写SQL*/
@RequiredArgsConstructor
public class MybatisDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {Logger logger = LoggerFactory.getLogger(MybatisDatabaseInterceptor.class);@Override // SELECT 场景public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {logger.debug("MybatisDatabaseInterceptor .... beforeQuery");if (!InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);//简单实现一个sql改造例子mpBs.sql(String.format(" select * from (%s) t limit 0,1", mpBs.sql()));}}@Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {logger.debug("MybatisDatabaseInterceptor .... beforePrepare");PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);MappedStatement ms = mpSh.mappedStatement();SqlCommandType sct = ms.getSqlCommandType();if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {return;}PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();mpBs.sql(this.parserMulti(mpBs.sql(), ms.getId()));}}@Overrideprotected void processSelect(Select select, int index, String sql, Object obj) {logger.debug("MybatisDatabaseInterceptor .... processSelect");
//        mpBs.sql(String.format(" select * from (%s) t limit 0,1", sql);}/*** update 语句处理*/@Overrideprotected void processUpdate(Update update, int index, String sql, Object obj) {logger.debug("MybatisDatabaseInterceptor .... processUpdate");}/*** delete 语句处理*/@Overrideprotected void processDelete(Delete delete, int index, String sql, Object obj) {logger.debug("MybatisDatabaseInterceptor .... processDelete");}
}

将拦截器注入Mybatis拦截器队列

@Configuration
public class DataPermissionConfiguration {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(List<DataPermissionRule> dataPermissionRule) {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();MybatisDatabaseInterceptor mybatisDatabaseInterceptor = new MybatisDatabaseInterceptor();List<InnerInterceptor> inners = new ArrayList<>(mybatisPlusInterceptor.getInterceptors());inners.add(0, mybatisDatabaseInterceptor);mybatisPlusInterceptor.setInterceptors(inners);return mybatisPlusInterceptor;}
}

完成上面配置后,每执行一个SQL都会被我们定义的拦截器拦截了,执行sql查询,可以看到已经过了拦截器。
在这里插入图片描述
至此我们完成了对SQL的拦截,并实现了最简单的sql改造。由于篇幅太长,对于sql的改造放到一下章:

总结,本章通过继承JsqlParserSupport 实现 InnerInterceptor 自定义SQL拦截器达到修改sql的目的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/283296.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录第三十四天(一刷C语言)|不同路径不同路径II

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、不同路径 思路&#xff1a;参考carl文档 机器人每次只能向下或者向右移动一步&#xff0c;机器人走过的路径可以抽象为一棵二叉树&#xff0c;叶子节点就是终点。 1、确定dp数组&#…

饥荒Mod 开发(十一):修改物品堆叠

饥荒Mod 开发(十)&#xff1a;制作一把AOE武器 饥荒Mod 开发(十二)&#xff1a;一键制作 饥荒中物品栏有限&#xff0c;要拾取的物品有很多&#xff0c;经常装不下要忍痛丢掉各种东西&#xff0c;即使可以将物品放在仓库但是使用不方便&#xff0c;所以可以将物品的堆叠个数设…

优质全套SpringMVC教程

三、SpringMVC 在SSM整合中&#xff0c;MyBatis担任的角色是持久层框架&#xff0c;它能帮我们访问数据库&#xff0c;操作数据库 Spring能利用它的两大核心IOC、AOP整合框架 1、SpringMVC简介 1.1、什么是MVC MVC是一种软件架构的思想&#xff08;不是设计模式-思想就是我们…

深度学习中的潜在空间

1 潜在空间定义 Latent Space 潜在空间&#xff1a;Latent &#xff0c;这个词的语义是“隐藏”的意思。“Latent Space 潜在空间”也可以理解为“隐藏的空间”。Latent Space 这一概念是十分重要的&#xff0c;它在“深度学习”领域中处于核心地位&#xff0c;即它是用来学习…

适用于 Windows 和 Mac 的 10 款最佳照片恢复软件(免费和付费)

丢失照片很容易。这里点击错误&#xff0c;那里贴错标签的 SD 卡&#xff0c;然后噗的一声&#xff0c;一切都消失了。值得庆幸的是&#xff0c;在技术领域&#xff0c;你可以纠正一些错误。其中包括删除数据或格式化错误的存储设备。 那么&#xff0c;让我们看看可用于从 SD …

docker文档转译1

写在最前面 本文主要是转译docker官方文档。主题是Docker overview&#xff0c;这里是链接 Docker概述 Docker是一个用于开发、发布和运行应用程序的开放平台。Docker使你能够将应用程序与基础设施分离&#xff0c;从而可以快速交付软件。你可以使用相同的方法像管理应用程序…

机器视觉技术与应用实战(开运算、闭运算、细化)

开运算和闭运算的基础是膨胀和腐蚀&#xff0c;可以在看本文章前先阅读这篇文章机器视觉技术与应用实战&#xff08;Chapter Two-04&#xff09;-CSDN博客 开运算&#xff1a;先腐蚀后膨胀。开运算可以使图像的轮廓变得光滑&#xff0c;具有断开狭窄的间断和消除细小突出物的作…

CentOS 7 部署 Nacos-2.3.0 (单机版)

CentOS 7 部署 Nacos-2.3.0 &#xff08;单机版&#xff09; 1. 下载 Nacos 安装包 历史版本&#xff1a;https://github.com/alibaba/nacos/releases/ 我选的是 2.3.0 版本&#xff0c;https://github.com/alibaba/nacos/releases/download/2.3.0/nacos-server-2.3.0.tar.g…

万兆网络之屏蔽线序接法(下)

我们直接干吧 1.剥去网线外被&#xff0c;这个相信大家都会的&#xff0c;剪掉尼龙线 剥去的长度用水晶头和护套量度&#xff0c;多留一点长度用于撸直 为了插进去很紧&#xff0c;我用的是超五类的护套&#xff0c;只能顶到条纹底端就很费劲了 然后十字骨架留一小段&#xf…

机器学习笔记 - 时间序列分析基础概念解释

一、简述 时间序列分析是一种统计方法,可检查定期收集的数据点以揭示潜在的模式。该技术与各个行业高度相关,因为它可以根据历史数据做出决策和预测。通过了解过去并预测未来,时间序列分析在金融、医疗保健、能源、供应链管理、天气预报、营销等领域发挥着至关重要的作用。 …

【经典LeetCode算法题目专栏分类】【第4期】BFS广度优先算法:单词接龙、最小基因变化、二进制矩阵中的最短路径

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 一般涉及到最小层数问题…

7.实现任务的rebalance

1.设计 1.1 背景 系统启动后&#xff0c;所有任务都在被执行&#xff0c;如果这时某个节点宕机&#xff0c;那它负责的任务就不能执行了&#xff0c;这对有稳定性要求的任务是不能接受的&#xff0c;所以系统要实现rebalance的功能。 1.2 设计 下面是Job分配与执行的业务点…