mybatis拦截器数据权限过滤

Mybatis拦截器之数据权限过滤与分页集成 需求场景 最近项目有个数据权限的业务需求,要求大致为每个单位只能查看本级单位及下属单位的数据,例如:一个集团军下属十二个旅,那么军级用户可以看到所有数据,而每个旅则只能看到本旅部的数据,以此类推; 解决方案之改SQL 原sql SELECT a.id AS "id", a.NAME AS "name", a.sex_cd AS "sexCd", a.org_id AS "... 阅读详情

1.项目结构

在这里插入图片描述

2.pom.xml

<groupId>org.example</groupId>
    <artifactId>spring-mybatis-source</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.12</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.4.2</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>4.4.5</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-cache</artifactId>
            <version>4.4.5</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.9</version>
        </dependency>

        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>commons-pool</groupId>
            <artifactId>commons-pool</artifactId>
            <version>1.6</version>
        </dependency>
    </dependencies>

3.基础配置

spring.xml

  <?xml version="1.0" encoding="UTF-8"?>
	<beans xmlns="http://www.springframework.org/schema/beans"
	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="maxActive" value="100"/>
        <property name="maxIdle" value="30"/>
        <property name="maxWait" value="500"/>
        <property name="defaultAutoCommit" value="true"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.lmh.dao.UserMapper"/>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="User" type="com.lmh.entity.User"/>
    </typeAliases>
    
    <plugins>
        <plugin interceptor="com.lmh.interceptor.PrivilegeExecutorInterceptor"/>
    </plugins>

    <mappers>
        <mapper resource="dao/UserMapper.xml"/>
    </mappers>
</configuration>

4.测试代码

User实体类 数据库字段随便建几个吧

package com.lmh.entity;

private Long id;

    private String username;

    private String password;

    private String email;

    private String headImage;

    private String countryName;

	// ...get set 

UserMapper

import com.lmh.entity.User;

public interface UserMapper {

   User findUserById(@Param("password") String password);
}

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.lmh.dao.UserMapper">

    <select id="findUserById" resultType="User">
        select
            id id,
            user_name username,
            user_password password,
            user_email email,
            head_image headImage,
            country_name countryName
        from sys_user where user_password = #{password}
        <![CDATA[
            <addAuth joiner = 'and' columnName = 'country_name' symbol = 'in' privilegeDimension='country'/>
        ]]>
    </select>
</mapper>

mybatis拦截器

@Intercepts(
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
)
public class PrivilegeExecutorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取executor对象
        Executor executor = (Executor)invocation.getTarget();
        //获取到mappedStatement
        MappedStatement ms = (MappedStatement)invocation.getArgs()[0];
        //查询参数
        Object parameterObject = invocation.getArgs()[1];
        //获取boundSql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        String sql = boundSql.getSql();
        if (sql.contains("<addAuth")) {
            sql = AddAuthParser.parse(sql);
        }
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), parameterObject);
        //这里没有使用缓存
        return executor.query(ms, parameterObject,  RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, null, newBoundSql);
    }

    /**
     * 注册拦截器
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
public class CountryPrivilegeInfo {

    /**
     * and、or
     */
    private String joiner = "and";

    /**
     * in、not in、 = 、<>等等
     * 这里案例只用 in
     */
    private String symbol = "in";

    /**
     * 数据库列名
     */
    private String columnName;

    /**
     * 权限维度。案例用的是country维度
     */
    private String privilegeDimension;

    /**
     * 用户上下文的值。实际工作中从登陆上下文中获取
     */
    private List<String> rolePrivileges = getCountryFromContext();

    public CountryPrivilegeInfo(String joiner, String symbol, String columnName, String privilegeDimension) {
        this.joiner = joiner;
        this.symbol = symbol;
        this.columnName = columnName;
        this.privilegeDimension = privilegeDimension;
    }

    /**
     * 获取用户所管理的国家信息
     */
    private List<String> getCountryFromContext() {
        List<String> list = new ArrayList<>();
        list.add("cn");
        list.add("us");
        return list;
    }
}

//省略 get set

解析类

public class AddAuthParser {

    /**
     * 暂时不考虑有多个<AddAuth>标签的情况
     */
    public static String parse(String sql) {
        StringBuilder finalSql = new StringBuilder();
        String[] preSplit = sql.split("<addAuth");
        String preSql = preSplit[1];
        String addAuthSql = preSql.substring(0, preSql.indexOf("/>"));
        String suffixSql = preSql.substring(preSql.indexOf("/>") + "/>".length());
        return finalSql.append(preSplit[0]).
                append(doParseAddAuthSql(addAuthSql)).
                append(suffixSql).toString();
    }

    /**
     * 解析<AddAuth>标签里面的内容
     */
    private static String doParseAddAuthSql(String sql) {
        //数据权限维度。实例工作中为地区、数据角色等维度
        String privilegeDimension = getValueByProperty("privilegeDimension", sql);
        String columnName = getValueByProperty("columnName", sql);
        String symbol = getValueByProperty("symbol", sql);
        String joiner = getValueByProperty("joiner", sql);
        CountryPrivilegeInfo countryPrivilegeInfo = new CountryPrivilegeInfo(joiner, symbol, columnName, privilegeDimension);
        StringBuilder privilegeStr = new StringBuilder(" ( ");
        List<String> rolePrivileges = countryPrivilegeInfo.getRolePrivileges();
        for (int i = 0; i < rolePrivileges.size(); i++) {
            privilegeStr.append("'").append(rolePrivileges.get(i)).append("'");
            if (i < rolePrivileges.size() - 1) {
                privilegeStr.append(", ");
            }
        }
        privilegeStr.append(" ) ");
        return " " + joiner + " "+ columnName + " "+ symbol + " " +privilegeStr;
    }

    private static String getValueByProperty(String property, String sql) {
        sql = sql.replace(" ", "");
        String open = property + "='";
        String close = "'";
        String[] split = sql.split(open);
        String value = split[1].substring(0, split[1].indexOf(close));
        return value.trim();
    }
}

5.测试类

public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        
  
        UserMapper userMapper = (UserMapper)context.getBean("userMapper");
        User userById = userMapper.findUserById("123456");
        System.out.println("userById = " + userById);

        
    }

最终打印日志sql

UserMapper.findUserById - ==>  Preparing: select id id, user_name username, user_password password, user_email email, head_image headImage, country_name countryName from sys_user where user_password = ? and country_name in ( 'cn', 'us' ) 

UserMapper.findUserById - ==> Parameters: 123456(String)

这样就实现了加上一个标签,就对登陆用户的数据权限进行了过滤

<![CDATA[
   <addAuth joiner = 'and' columnName = 'country_name' symbol = 'in' privilegeDimension='country'/>
]]>
mybatis 拦截器 拦截器可在mybatis进行sql底层处理的时候执行额外的逻辑,最常见的就是分页逻辑、对结果集进行处理过滤敏感信息等。 阅读详情

相关推荐

springboot自定义注解+mybatis拦截器实现数据权限

基本所有的系统都会涉及菜单访问权限和数据访问权限。对于菜单访问权限一般实现方式都差不多,用户登录时加载具有访问权限的菜单,然后进行展示,用户访问菜单时通过统一的拦截器服务器端再次判断是有具有访问权限,防止前端直接url越权访问;对于细粒度具体到按钮级别的访问控制,实现方式也差不多,一个具体到菜单访问url,一个具体到方法访问url,对于菜单访问权限设计实现此文不做过多介绍。对于数据权限,一般需要根据系统实际的业务场景进行设计实现,那些脱离业务谈数据权限都是扯淡的。参与的一个项目,需要实现如下数据权限,可以设

guaotianxia的博客 4989

MyBatis Plus 拦截器实现数据权限控制(完整版)

新增针对不需要做数据权限控制的注解

progammer10086的博客 8103

SpringBoot+MyBatis数据权限实战:基于拦截器的SQL动态过滤方案

数据权限是企业级应用安全架构的核心组成部分,它确保不同角色的用户在同一功能界面下只能访问授权范围内的数据。其实现原理是在数据库查询执行时动态注入过滤条件,从而在数据源头实现访问控制。这一技术对于保障系统安全、满足合规要求具有关键价值,广泛应用于金融、医疗、企业管理等对数据敏感度高的行业场景。通过MyBatis拦截器机制,可以在SQL执行前自动拼接权限条件,实现与业务逻辑的完全解耦。本文结合SpringBoot框架,深入探讨如何设计可解析的权限规则引擎,并解决多表关联、分页查询等工程实践中的典型问题,为构建健

weixin_33933118的博客 403

mybatis拦截器实现数据权限

前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现。比如员工打卡记录表,有id,name,dpt_id,company_id等字段,后两个表示部门ID和分公司ID。当一个总部账号可以查看全部数据此时,sql无需改变。因为他可以看到全部数据。当一个部门管理员权限员工查看全部数据时,sql需要在末属添加如果每个功能模块都需要手动写代码去拿到当前登陆用户的所属部门,然后手动添加where条件,就显得非常的繁琐。

BASK2311的博客 1422

集成calibre到cadence virtuoso环境中 load: can‘t access file - “/opt/eda/Mentor/lib/calibre.skl“

项目场景: 设想是将Calibre集成到cadence virtuoso中,但网上的解决办法尝试过在本机上实验都有点问题,因此想记录下来我的解决办法仅供参考。 问题描述: 安装好cadence, calibre之后打开virtuoso,CIW窗口显示 *Error* load: can't access file - "/opt/eda/Mentor/lib/calibre.skl" 问题是出在calibre.skl的加载路径出错,尝试网上的在工程目录下修改.cdsinit文件,添加calibre安装

weixin_52714276的博客 1万+

王道考研操作系统笔记(第一章)附:王道考研408所有PPT和思维导图

学习感想 别人的笔记对你的学习基本上没有用处,容易培养人的惰性,如需ppt和思维导图请拉到最后,有github链接 记笔记并不是一件浪费时间的事情,还有利于多次学习 真心希望大家在学习的过程中记笔记 操作系统 第一章 计算机系统概述 1.1 操作系统 1.1.1 操作系统的概念和功能 概念 操作系统(Operating System, OS)是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配;以提供给用户和其他软件方便的接口和环境;它是计算机系统中最基本的系统软

qq_41732756的博客 1万+

MyBatis 拦截器实现通用数据权限过滤

本文介绍了一种基于MyBatis拦截器实现通用数据权限过滤的方案。该方案通过在SQL执行前自动添加过滤条件,实现用户级、部门级、组织级等多维度的数据隔离。核心设计包括选择StatementHandler作为拦截点、7级过滤规则优先级判断机制以及性能优化策略(如LRU缓存)。方案具有零侵入、高安全性和高扩展性等特点,适用于SaaS多租户、企业OA等多种业务场景。通过SQL自动改写实现数据权限控制,避免了传统方式在每个SQL中手动添加条件的重复工作。

一缕南风的博客 1175

别再只看成交量了!用通达信这个VOL监测公式,一眼识别主力吸筹与出货信号

本文详细介绍了通达信VOL监测公式的实战应用,通过彩色量柱和换手率分层技术,帮助投资者识别主力吸筹与出货信号。该公式结合动态量能标准化和多周期均线组合,提供可视化主力行为图谱,提升交易决策的准确性。

weixin_28693447的博客 656

mysql 过滤分页_Mybatis拦截器之数据权限过滤与分页集成

解决方案之改SQL原sqlSELECTa.id AS "id",a.NAME AS "name",a.sex_cd AS "sexCd",a.org_id AS "orgId",a.STATUS AS "status",a.create_org_id AS "createOrgId"FROMpty_person aWHEREa. STATUS = 0org_id是单位的标识,也就是where条件里...

weixin_39997695的博客 556

接入穿山甲SDK时,Unity打Android包报错日志

SDK工程设置为Enable,项目工程设置为Disable。2、将unitypackage导入包名为:com.DefaultCompany.Myproject的另一个工程,打包报错。3、点击工程默认弹出Enable Android Auto-resolution 设置选项,点击Enable。1、新建一个空工程,设置包名为:com.ceshi.hewen,正常打包无报错,导出unitypackage。2、点击Reset to Defaults 重置参数,再点击ok关闭设置窗口。

不文不武的禾文 3582

网表导入virtuoso后发现pg pin忘记connect_pg_net/globalNetConnect怎么办?

数模混合项目中经常需要一些需要到virtuoso去连接的线,比如IO上的pg和一些信号线,除了在pr工作中设置skip route之外还需要做好net的赋值,告诉工具虽然我没在物理上有连接,但是实际上应该连什么net,这部分提现在网表里。在Default Net Name中也填写net name,因为这个net需要做global操作,即在顶层定义这个net,所以需要在net name后加个!打开schematic,删除指定cell pin所接net的label。

m0_61544122的博客 967

基于Mybatis拦截器实现数据权限过滤

intercept方法这个方法是核心,当拦截到调用时会执行。Invocation 对象包含了被拦截方法的所有信息,包括方法本身、参数、目标对象等。在这个方法中,你可以做任何预处理或后处理逻辑,然后通过调用 invocation.proceed() 来继续执行原方法,或者直接返回自定义的结果。plugin方法这个方法用于决定是否对某个对象应用拦截器。如果返回 target,则表示不进行拦截;

qq_43613885的博客 1017

数据库常见面试题(附答案)

1.事务四大特性 原子性,要么执行,要么不执行 隔离性,所有操作全部执行完以前,其它会话不能看到过程 一致性,事务前后,数据总额一致 持久性,一旦事务提交,对数据的改变就是永久的 2.数据库隔离级别,每个级别会引发什么问题,mysql默认是哪个级别 脏读:事务B读取事务A还没有提交的数据 不可重复读:两次事务读的数据不一致 幻读:事务A修改了数据,事务B也修改了数据,这时在事务A看...

csdn问鼎 46万+

使用mybaits插件实现数据权限

在业务系统开发中,数据权限的需求是少不了的.大多数都是根据操作人的角色限制数据范围,例如只能查询某些部门,类型或者用户的数据.

学习笔记 972

飞牛 NAS 部署 Hermes Agent 全指南:打造 24 小时在线、自我进化的 AI 智能体!

当大多数人还在把 AI 当聊天机器人使用时,我已经把它部署到了家里的飞牛 NAS 上,让它 24 小时在线工作、记忆我的习惯、自动执行任务,甚至还能不断学习新的技能。今天带你从零搭建这个真正属于自己的 AI Agent。🚀

XiaoqiangClub的博客 1218

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Java数据权限-基于mybatis

数据权限

m0_56086190的博客 792

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Springboot管理系统数据权限过滤(四)——mybatis数据权限插件

Springboot管理系统数据权限过滤(三)——0业务入侵实现部门数据权限过滤数据权限实现的思路和代码实现已经了解。本节在此基础上实现支持mybatis框架的通用数据过滤插件。 实现目标:上一章示例,使用的是mytatisplus数据持久框架,使用是的mybatisplus拦截器,本章是基于mybatis持久化框,所以仅拦截器实现上有一点点差异,但我们也是可以引入mybatisplus的依赖包,但仅使用它的对SQL的处理上,这样可以省很多事。除了这一点,其他的代码都可以沿用上一章的内容。该项目初衷

朗朗的博客 2214

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

mybatis update set 多个字段_开发MyBatis自定义Interceptor拦截器的使用

开发MyBatis自定义Interceptor拦截器的使用一、使用背景 笔者前几天分享的在Spring Cloud架构中,关于数据权限的处理。笔者在团队中采用的就是通过MyBatis拦截器,拦截sql语句,动态组装sql来完成添加数据权限的配置,本文笔者分享MyBatis自定义Interceptor的使用。二、拦截器注解 mybatis实现自定义拦截器实现过程: (1)实现org.apache.i...

weixin_39762666的博客 761

从零搭建开发脚手架 基于Mybatis-Plus的数据权限实现

文章目录权限分类数据权限实现MyBatis的拦截器简介具体实现数据库设计权限类型设计拦截器实现参数传递加载拦截器忽略拦截使用示例 今天来整理分享下常用的权限设计,以及基于Mybatis-plus实现的一套数据权限的实现。 权限分类 功能权限:能看到什么功能模块,能操作什么功能,增删改查实体等。(常见) 数据权限:能看到哪些数据,例如管理员能看到所有人的数据,普通员工只能看到自己的数据,领导能看到本部门的数据。(常见) 字段权限:能看到哪些字段,例如普通员工看不到自己剩余的请假天数,领导和管理员可以。(不常

laker的博客 8124

Mybatis自定义拦截器与插件开发

在Spring中我们经常会使用到拦截器,在登录验证、日志记录、性能监控等场景中,通过使用拦截器允许我们在不改动业务代码的情况下,执行拦截器的方法来增强现有的逻辑。在mybatis中,同样也有这样的业务场景,有时候需要我们在不侵入原有业务代码的情况下拦截sql,执行特定的某些逻辑。那么这个过程应该怎么实现呢,同样,在mybatis中也为开发者预留了拦截器接口,通过实现自定义拦截器这一功能,可以实现我们自己的插件,允许用户在不改动mybatis的原有逻辑的条件下,实现自己的逻辑扩展。 那么,在实现拦截器之前,首

1561

MyBatis-Plus数据权限拦截器多条件IN查询实现指南

MyBatis-Plus数据权限拦截器多条件IN查询实现指南 【免费下载链接】mybatis-plus mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com ...

gitblog_01409的博客 908
上一篇: spring-mybatis源码分析之spring如何获取到UserMapper实例的?
下一篇: MybatisPlus源码解析2:mybatis-plus启动流程之Sql注入器-SqlInjector
hao9e
博客等级 码龄7年 189粉丝 51原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值