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

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

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


攔截器基本知識


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

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

具體規(guī)則如下:

?a:Intercepts?標識我的類是一個攔截器

?b:Signature 則是指明我們的攔截器需要攔截哪一個接口的哪一個方法陨晶;type對應四類接口中的某一個,比如是 Executor珍逸;method對應接口中的哪類方法聋溜,比如 Executor 的 update 方法;args?對應接口中的哪一個方法,比如 Executor 中 query 因為重載原因叭爱,方法有多個撮躁,args 就是指明參數(shù)類型买雾,從而確定是哪一個方法。

@Intercepts({

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

})

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

(3)攔截器順序:

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

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


數(shù)據(jù)權限過濾


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

?? 2.采用技術:mybatis攔截器媳禁,java自定義注解,反射侦啸,開源jsqlparser

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


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ù)權限注解

*

*/

@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方法增加權限注解

```

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> {

? ? /**

? ? * 增加權限注解

? ? *

? ? */

? ? @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ù)權限攔截器

* 根據(jù)各個微服務,繼承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())) {

? ? ? ? ? ? ? ? /**

? ? ? ? ? ? ? ? * 查找標識了該注解 的實現(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è)務 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();

? ? }

? ? /**

? ? * 當目標類是Executor類型時,才包裝目標類齐佳,否者直接返回目標本身,減少目標被代理的次數(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è)務邏輯

```

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

? ? /**

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

? ? *

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

? ? }

}

```

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末炼吴,一起剝皮案震驚了整個濱河市疫衩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌童芹,老刑警劉巖鲤拿,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異生音,居然都是意外死亡,警方通過查閱死者的電腦和手機久锥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門瑟由,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歹苦,你說我怎么就攤上這事『萁牵” “怎么了蚪腋?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵瞧挤,是天一觀的道長召噩。 經(jīng)常有香客問我,道長晓勇,這世上最難降的妖魔是什么灌旧? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮描融,結果婚禮上衡蚂,老公的妹妹穿的比我還像新娘。我一直安慰自己讳窟,他們只是感情好丽啡,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布硬猫。 她就那樣靜靜地躺著改执,像睡著了一般坑雅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上终蒂,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天遥诉,我揣著相機與錄音,去河邊找鬼霉翔。 笑死苞笨,一個胖子當著我的面吹牛,可吹牛的內容都是我干的瀑凝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼芝加,長吁一口氣:“原來是場噩夢啊……” “哼射窒!你這毒婦竟也來了?” 一聲冷哼從身側響起蝌麸,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤艾疟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弟疆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盗冷,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年柑司,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟆湖。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡玻粪,死狀恐怖,靈堂內的尸體忽然破棺而出奶段,到底是詐尸還是另有隱情,我是刑警寧澤呢铆,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布蹲缠,位于F島的核電站,受9級特大地震影響娜谊,放射性物質發(fā)生泄漏斤讥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一派草、第九天 我趴在偏房一處隱蔽的房頂上張望铛楣。 院中可真熱鬧,春花似錦簸州、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛆橡,卻和暖如春掘譬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背葱轩。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工靴拱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垃喊,地道東北人袜炕。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓偎窘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陌知。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容