最近在做一個測試平台,其中有一個需求是用戶只能看到他有權限的項目數據。一開始這個需求只針對用例模塊,我直接在sql后面加上了關聯項目權限表。后面因為其他模塊也需要這個權限判斷,故打算把關聯sql抽取出來,實現攔截sql並添加權限。
大概分為三步:
1、定義一個注解 PermissionAop (可以在注解中添加參數,實現不同參數做不同的sql拼接)
2、實現攔截器(參考pagehelper的PageInterceptor實現),攔截Executor,當遇到mapper方法帶有注解A,把權限的sql拼接上去
3、添加攔截器,可以使用@Configuration或實現ApplicationListener(自行百度),先添加pagehelper攔截器,再添加自定義的數據權限攔截器
下面說下要注意的點:
1、注意pom文件引入的是pagehelper-spring-boot-starter還是pagehelper。引入pagehelper-spring-boot-starter會自動添加pagehelper攔截器,而引入pagehelper則要手動添加攔截器。(我使用的pagehelper,下面偽代碼有。若使用pagehelper-spring-boot-starter,參考https://blog.csdn.net/weixin_45497155/article/details/106025321)
2、測試時發現生成的sql總是先limit再權限過濾,pagehelper的攔截一直優先於自定義的攔截(這樣會出現limit出10條,權限過濾剩5條,實際要求是先權限過濾,再limit出10條),一開始以為是添加攔截器的順序問題,后面在官方文檔看到,是攔截的對象不對(百度很多文章寫的都是攔截StatementHandler),應該要攔截Executor
詳細參考pagehelper官方文檔:https://gitee.com/free/Mybatis_PageHelper/blob/master/wikis/zh/Interceptor.md
偽代碼:
注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author: Hjy * @Date: 2021/8/18 15:38 (日期和時間) * @description 自定義權限Aop注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface PermissionAop { PermissionEnum type() default PermissionEnum.DEFAULT; }
攔截器:
/** * @Author: Hjy * @Date: 2021/11/4 15:01 (日期和時間) * @description sql權限攔截(不能攔截StatementHandler , pageHelper攔截的是Executor *具體參考 : https://gitee.com/free/Mybatis_PageHelper/blob/master/wikis/zh/Interceptor.md ) */ @Intercepts({@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} )}) public class SqlAuthenticationInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 這一段復制pageHelper的PageInterceptor Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) invocation.getTarget(); CacheKey cacheKey; BoundSql boundSql; if (args.length == 4) { boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } // 獲取全類名和方法名 String id = ms.getId(); String className = id.substring(0, id.lastIndexOf(".")); String methodName = id.substring(id.lastIndexOf(".") + 1); final Class<?> cls = Class.forName(className); final Method[] methods = cls.getMethods(); for (Method method : methods) { // 若該方法含有PermissionAop注解,添加權限sql if (method.getName().equals(methodName) && method.isAnnotationPresent(PermissionAop.class)) { String permissionSql = getPermissionSql(boundSql.getSql()); ReflectUtil.setFieldValue(boundSql, "sql", permissionSql); } } return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private String getPermissionSql(String boundSql) { // 這里拼接你的權限sql return boundSql+""; } }
配置攔截器
/** * @Author: Hjy * @Date: 2021/11/10 15:39 (日期和時間) * @description 自定義mybatis的攔截,不使用pagehelper-spring-boot-starter,目的是添加攔截數據權限 */ @Configuration public class MybatisInterceptorAutoConfiguration { @Resource private List<SqlSessionFactory> sqlSessionFactoryList; @Bean @ConfigurationProperties(prefix = "pagehelper") public Properties pageHelperProperties() { return new Properties(); } @PostConstruct public void addMysqlInterceptor() { //數據權限攔截器 SqlAuthenticationInterceptor sqlAuthenticationInterceptor = new SqlAuthenticationInterceptor(); //分頁攔截器 PageInterceptor pageInterceptor = new PageInterceptor(); pageInterceptor.setProperties(this.pageHelperProperties()); // 先增加的攔截后執行 for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor); // 增加權限攔截 sqlSessionFactory.getConfiguration().addInterceptor(sqlAuthenticationInterceptor); } } }