在項目中, 針對敏感數據等重要數據的列表查詢,爲避免數據泄露等, 須要針對數據添加權限。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