最近在做一個測試平台,其中有一個需求是用戶只能看到他有權限的項目數據。一開始這個需求只針對用例模塊,我直接在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);
}
}
}
