mybatis自定義攔截器-數(shù)據(jù)權(quán)限過濾

? ?最近一段時間公司搞新項目,數(shù)據(jù)庫orm選用了mybatis框架。使用一段時間mybaits后感覺比其他orm框架靈活好用,好處就不說了,網(wǎng)上一搜大把。本次主要講下mybatis自定義攔截器功能的開發(fā),通過攔截器可以解決項目中蠻多的問題,雖然很多功能不用攔截器也可以實現(xiàn),但使用自定義攔截器實現(xiàn)功能從我角度至少以下優(yōu)點(1)靈活,解耦(2)統(tǒng)一控制 ,減少開發(fā)工作量,不用散落到每個業(yè)務(wù)功能點去實現(xiàn)。

? ? 一般業(yè)務(wù)系統(tǒng)項目都涉及到數(shù)據(jù)權(quán)限的控制,此次結(jié)合本項目記錄下基于mybatis攔截器實現(xiàn)數(shù)據(jù)權(quán)限的過濾,因為項目用到mybatis-plus的分頁插件,數(shù)據(jù)權(quán)限攔截過濾的時機(jī)也要控制好,在分頁攔截器之前先攔截修改sql,不然會導(dǎo)致查詢出來的數(shù)據(jù)同分頁統(tǒng)計出來數(shù)量不一致。


攔截器基本知識


? ? Mybatis采用責(zé)任鏈模式,通過動態(tài)代理組織多個攔截器,通過這些攔截器可以改變mybatis的默認(rèn)行為,編寫自定義攔截器最好了解下它的原理,以便寫出安全高效的插件。

?(1)攔截器均需要實現(xiàn)org.apache.ibatis.plugin.Interceptor 接口,對于自定義攔截器必須使用mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口。

具體規(guī)則如下:

?a:Intercepts?標(biāo)識我的類是一個攔截器

?b:Signature 則是指明我們的攔截器需要攔截哪一個接口的哪一個方法;type對應(yīng)四類接口中的某一個,比如是 Executor;method對應(yīng)接口中的哪類方法,比如 Executor 的 update 方法;args?對應(yīng)接口中的哪一個方法,比如 Executor 中 query 因為重載原因,方法有多個,args 就是指明參數(shù)類型,從而確定是哪一個方法。

@Intercepts({

??? @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

})

(2)?mybatis 攔截器默認(rèn)可攔截的類型四種,即四種接口類型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler,對于我們的自定義攔截器必須使用 mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口。

(3)攔截器順序:

?不同類型攔截器的順序Executor -> ParameterHandler -> StatementHandler ->ResultSetHandler

??同類型的攔截器的不同對象攔截順序則根據(jù) mybatis 核心配置文件的配置位置,攔截順序是 從上往下,在mybatis 核心配置文件中需要配置我們的 plugin?


數(shù)據(jù)權(quán)限過濾


???1.實現(xiàn)業(yè)務(wù)需求的數(shù)據(jù)過濾,在用戶訪問數(shù)據(jù)庫時進(jìn)行權(quán)限判斷并改造sql,達(dá)到限制低權(quán)限用戶訪問數(shù)據(jù)的目的

?? 2.采用技術(shù):mybatis攔截器,java自定義注解,反射,開源jsqlparser

?? 3.核心業(yè)務(wù)流程圖


4.代碼實現(xiàn)

(1)創(chuàng)建自定義注解

```

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 數(shù)據(jù)權(quán)限注解

*

*/

@Documented

@Target( value = { ElementType.TYPE, ElementType.METHOD } )

@Retention( RetentionPolicy.RUNTIME )

@Inherited

public @interface DataAuth

{

/**

* 追加sql的方法名

* @return

*/

public String method() default "whereSql";

/**

* 表別名

* @return

*/

public String tableAlias() default "";

}

```

(2)mapper方法增加權(quán)限注解

```

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface TestMapper extends BaseMapper<Test> {

? ? /**

? ? * 增加權(quán)限注解

? ? *

? ? */

? ? @DataAuth(tableAlias = "o")

? ? List<TestEntity> listData(TestQuery testQuery);

}

```

(3)創(chuàng)建自定義攔截器

```

import net.sf.jsqlparser.expression.Expression;

import net.sf.jsqlparser.expression.Parenthesis;

import net.sf.jsqlparser.expression.operators.conditional.AndExpression;

import net.sf.jsqlparser.parser.CCJSqlParserManager;

import net.sf.jsqlparser.parser.CCJSqlParserUtil;

import net.sf.jsqlparser.statement.select.PlainSelect;

import net.sf.jsqlparser.statement.select.Select;

import org.apache.commons.lang3.StringUtils;

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.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Plugin;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.reflection.DefaultReflectorFactory;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

import org.springframework.stereotype.Component;

import java.io.StringReader;

import java.lang.reflect.Method;

import java.util.Map;

import java.util.Properties;

/**

* 數(shù)據(jù)權(quán)限攔截器

* 根據(jù)各個微服務(wù),繼承DataAuthService增加不同的where語句

*

*/

@Component

@Intercepts({@Signature(method = "query",type = Executor.class,args =? {

? ? ? ? MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}

? ? )

})

public class MybatisDataAuthInterceptor implements Interceptor,

? ? ApplicationContextAware {

? ? private final static Logger logger = LoggerFactory.getLogger(MybatisDataAuthInterceptor.class);

? ? private static ApplicationContext context;

? ? @Override

? ? public void setApplicationContext(ApplicationContext applicationContext)

? ? ? ? throws BeansException {

? ? ? ? context = applicationContext;

? ? }

? ? @Override

? ? public Object intercept(Invocation arg0) throws Throwable {

? ? ? ? MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];

? ? ? ? // 只對查詢sql攔截

? ? ? ? if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {

? ? ? ? ? ? return arg0.proceed();

? ? ? ? }

? ? ? ? // String mSql = sql;

? ? ? ? // 注解邏輯判斷 添加注解了才攔截追加

? ? ? ? Class<?> classType = Class.forName(mappedStatement.getId()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .substring(0,

? ? ? ? ? ? ? ? ? ? mappedStatement.getId().lastIndexOf(".")));

? ? ? ? String mName = mappedStatement.getId()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .substring(mappedStatement.getId()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .lastIndexOf(".") +

? ? ? ? ? ? ? ? 1, mappedStatement.getId().length()); //

? ? ? ? for (Method method : classType.getDeclaredMethods()) {

? ? ? ? ? ? if (method.isAnnotationPresent(DataAuth.class) &&

? ? ? ? ? ? ? ? ? ? mName.equals(method.getName())) {

? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? * 查找標(biāo)識了該注解 的實現(xiàn) 類

? ? ? ? ? ? ? ? */

? ? ? ? ? ? ? ? Map<String, Object> beanMap = context.getBeansWithAnnotation(DataAuth.class);

? ? ? ? ? ? ? ? if ((beanMap != null) && (beanMap.entrySet().size() > 0)) {

? ? ? ? ? ? ? ? ? ? for (Map.Entry<String, Object> entry : beanMap.entrySet()) {

? ? ? ? ? ? ? ? ? ? ? ? DataAuth action = method.getAnnotation(DataAuth.class);

? ? ? ? ? ? ? ? ? ? ? ? if (StringUtils.isEmpty(action.method())) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? ? ? Method md = entry.getValue().getClass()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .getMethod(action.method(),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Class[] { String.class });

? ? ? ? ? ? ? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? ? ? ? ? ? ? * 反射獲取業(yè)務(wù) sql

? ? ? ? ? ? ? ? ? ? ? ? ? ? */

? ? ? ? ? ? ? ? ? ? ? ? ? ? String whereSql = (String) md.invoke(context.getBean(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? entry.getValue().getClass()),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Object[] { action.tableAlias() });

? ? ? ? ? ? ? ? ? ? ? ? ? ? if (!StringUtils.isEmpty(whereSql) &&

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? !"null".equalsIgnoreCase(whereSql)) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Object parameter = null;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (arg0.getArgs().length > 1) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? parameter = arg0.getArgs()[1];

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BoundSql boundSql = mappedStatement.getBoundSql(parameter);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MappedStatement newStatement = newMappedStatement(mappedStatement,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new BoundSqlSqlSource(boundSql));

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MetaObject msObject = MetaObject.forObject(newStatement,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultObjectFactory(),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultObjectWrapperFactory(),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultReflectorFactor());

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? * 通過JSqlParser解析 原有sql,追加sql條件

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? */

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CCJSqlParserManager parserManager = new CCJSqlParserManager();

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Select select = (Select) parserManager.parse(new StringReader(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? boundSql.getSql()));

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PlainSelect selectBody = (PlainSelect) select.getSelectBody();

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.setWhere(new AndExpression(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.getWhere(),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Parenthesis(whereExpression)));

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? * 修改sql

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? */

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? msObject.setValue("sqlSource.boundSql.sql",

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.toString());

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? arg0.getArgs()[0] = newStatement;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? logger.info("Interceptor sql:" +

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.toString());

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? logger.error(null, e);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return arg0.proceed();

? ? }

? ? 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();

? ? }

? ? /**

? ? * 當(dāng)目標(biāo)類是Executor類型時,才包裝目標(biāo)類,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的次數(shù)

? ? */

? ? @Override

? ? public Object plugin(Object target) {

? ? ? ? if (target instanceof Executor) {

? ? ? ? ? ? return Plugin.wrap(target, this);

? ? ? ? }

? ? ? ? return target;

? ? }

? ? @Override

? ? public void setProperties(Properties arg0) {

? ? ? ? // TODO Auto-generated method stub

? ? }

? ? class BoundSqlSqlSource implements SqlSource {

? ? ? ? private BoundSql boundSql;

? ? ? ? public BoundSqlSqlSource(BoundSql boundSql) {

? ? ? ? ? ? this.boundSql = boundSql;

? ? ? ? }

? ? ? ? @Override

? ? ? ? public BoundSql getBoundSql(Object parameterObject) {

? ? ? ? ? ? return boundSql;

? ? ? ? }

? ? }

}

```

(4)增加業(yè)務(wù)邏輯

```

import com.baomidou.mybatisplus.core.toolkit.StringUtils;

import com.winhong.wincloud.constant.RoleTypeJudge;

import com.winhong.wincore.async.ThreadLocalHolder;

import com.winhong.wincore.user.LoginUserHolder;

import com.winhong.wincore.user.UserInfo;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Service;

@Service

public abstract class AbstractDataAuthService {

? ? private static final Logger LOG = LoggerFactory.getLogger(AbstractDataAuthService.class);

? ? /**

? ? * 默認(rèn)查詢sql,根據(jù)角色不同追加不同業(yè)務(wù)查詢條件

? ? *

? ? * @return

? ? */

? ? public String whereSql(String tableAlias) {

? ? ? ? if (!StringUtils.isEmpty(tableAlias)) {

? ? ? ? ? ? tableAlias = tableAlias + ".";

? ? ? ? }

? ? ? ? StringBuffer sql = new StringBuffer();

? ? ? ? //利用threadlocal獲取用戶角色信息

? ? ? ? UserInfo userInfo = LoginUserHolder.getUser();

? ? ? ? // 普通 用戶

? ? ? ? if (RoleTypeJudge.isNormalUser(userInfo.getRoleTypeCode())) {

? ? ? ? ? ? sql.append(nomalUserSql(userInfo.getUserUuid(), tableAlias));

? ? ? ? }

? ? ? ? // 管理員

? ? ? ? else if (RoleTypeJudge.isManager(userInfo.getRoleTypeCode())) {

? ? ? ? ? ? sql.append(managerSql(tableAlias));

? ? ? ? } else {

? ? ? ? }

? ? ? ? return sql.toString();

? ? }

}

```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容