在项目中, 针对敏感数据等重要数据的列表查询,为避免数据泄露等, 须要针对数据添加权限。java
场景:有三个帐号:员工A、员工B、经理A;专员A、B 属于经理A的下级,且他们属于同一部门; 在数据库中的客户列表中, 员工A、B只能查看本身服务的客户列表, 经理A能够查看该部门的客户列表,不能查看其余部门的客户列表;在这中场景下,就须要应用到数据权限了。mysql
为了方便不影响其余业务编码,下降耦合, 在底层SQL执行前拦截根据权限条件添加WHERE条件,进行动态更改SQLgit
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency>
package com.richfun.boot.common.config; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; import java.util.Properties; /** * MyBatis SQL拦截 * @author geYang 2019-07-16 * */ @Component @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) public class MBSqlInterceptor implements Interceptor { @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} @Override public Object intercept(Invocation invocation) throws Throwable { String sqlType = getSqlType(invocation); if (!SELECT.equals(sqlType)) { return invocation.proceed(); } String sqlId = getSqlId(invocation); System.out.println("SQL__ID: " + sqlId); String sql = getSql(invocation); System.out.println("原始SQL: " + sql); String updateSql = getUpdateSql(sql); System.out.println("查询SQL: " + updateSql); if (!sql.equals(updateSql)) { updateSql(invocation, updateSql); } return invocation.proceed(); } /** * 拦截编辑后的SQL * */ private String getUpdateSql(String sql) { StringBuilder updateSql = new StringBuilder(sql); // 各类数据权限拦截 boolean is_where = sql.contains("WHERE") || sql.contains("where"); if (is_where) { updateSql.append(" AND user_id > 1"); } else { updateSql.append(" WHERE user_id > 1"); } return updateSql.toString(); } /** * 获取SQL类型 * */ private static final String SELECT = "SELECT", INSERT = "INSERT", UPDATE = "UPDATE", DELETE = "DELETE"; private static String getSqlType(Invocation invocation) { final Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; SqlCommandType commandType = ms.getSqlCommandType(); if (commandType.compareTo(SqlCommandType.SELECT) == 0) { return SELECT; } if (commandType.compareTo(SqlCommandType.INSERT) == 0) { return INSERT; } if (commandType.compareTo(SqlCommandType.UPDATE) == 0) { return UPDATE; } if (commandType.compareTo(SqlCommandType.DELETE) == 0) { return DELETE; } return null; } /** * 获取SQL-ID * */ private String getSqlId(Invocation invocation) { final Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; return ms.getId(); } /** * 获取SQL语句 * */ private String getSql(Invocation invocation) { final Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; BoundSql boundSql = ms.getBoundSql(parameterObject); return boundSql.getSql(); } /** * 修改SQL语句 * */ private void updateSql(Invocation invocation, String sql) { final Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; BoundSql boundSql = ms.getBoundSql(parameterObject); MappedStatement newStatement = newMappedStatement(ms, new BoundSqlSqlSource(boundSql)); MetaObject msObject = SystemMetaObject.forObject(newStatement); msObject.setValue("sqlSource.boundSql.sql", sql); args[0] = newStatement; } /** * SQL * */ private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) { MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.fetchSize(ms.getFetchSize()); builder.statementType(ms.getStatementType()); builder.keyGenerator(ms.getKeyGenerator()); if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) { StringBuilder keyProperties = new StringBuilder(); for (String keyProperty : ms.getKeyProperties()) { keyProperties.append(keyProperty).append(","); } keyProperties.delete(keyProperties.length() - 1, keyProperties.length()); builder.keyProperty(keyProperties.toString()); } builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.resultSetType(ms.getResultSetType()); builder.cache(ms.getCache()); builder.flushCacheRequired(ms.isFlushCacheRequired()); builder.useCache(ms.isUseCache()); return builder.build(); } /** * SQL 绑定工具 * */ private class BoundSqlSqlSource implements SqlSource { private BoundSql boundSql; BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } @Override public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } }
<?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> <!-- 自定义SQL拦截, 执行顺序: 从下往上 --> <plugins> <!-- 分页拦截 (不用分页,不要也能够)--> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> </plugin> <!-- SQL拦截 --> <plugin interceptor="com.gy.spring.mvc.common.interceptor.MBSqlInterceptor" /> </plugins> </configuration>
@Test public void testDao() { // PageHelper.startPage(1, 10); List<Map<String, Object>> list = commonDao.list("UserMapper.listUser", null); System.out.println(list); }
SQL拦截基于动态代理的方式来实现, 只需实现 Interceptor 接口便可;github
Mybatis-PageHelper: https://github.com/pagehelper/Mybatis-PageHelper spring
PageHelper拦截文档: https://pagehelper.github.io/docs/interceptor/sql