Design and Implementation of Data Permission in Backend Projects Using MyBatis Interceptor and Spring Boot Auto‑Configuration
This article explains a low‑intrusion data‑permission design for backend micro‑services, detailing resource and value‑rule definitions, RBAC modeling, MyBatis SQL interception implementation, Redis‑based permission retrieval, and Spring Boot auto‑configuration for easy enablement in production.
Background – In many small backend projects, developers often hard‑code data‑permission filters directly into SQL queries. While this works for a single module, it becomes invasive and labor‑intensive when the system is split into micro‑services, requiring changes in every module.
Data‑Permission Design – The design aims for low or zero code intrusion and one‑click enable/disable. Permissions are expressed as resources (e.g., a table column such as createUserId) and value‑rules that define how the column is filtered (e.g., createUserId=1 for “own records”).
Detailed Design
Use an RBAC (Role‑Based Access Control) model.
Resource design links a request URL → mapper method → database table, allowing permission checks only on relevant queries.
Value‑rule types include: own‑record, department‑visible, department‑and‑sub‑departments, specific value, and custom SQL.
Examples:
Own‑record: Retrieve the current user ID via a full‑path Spring bean method and append createUserId=1 to the SQL.
Specific value: For a group leader to see all orders of their group, use a predefined value.
Custom: Use a custom SQL fragment such as order_amount>100000 or a custom method to fetch company‑wide IDs.
SQL Execution Interception – Implemented with MyBatis. The interceptor follows the pattern of MyBatis PageHelper, intercepting the Executor.query method, extracting the bound SQL, and delegating to a helper that appends permission conditions.
@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})
})
@Slf4j
public class DataPermissionsHandlerInterceptor implements Interceptor {
@Resource(name = "org.demo.common.permission.dialect.RedisMysqlDataPermissionDialect")
private DataPermissionDialect dialect;
@Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
try {
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];
}
List<String> allTableName = null;
Map<String, Map<String, DataPermissionGetValueVO>> dataPermission = null;
return ExecuteSqlContext.doResponsibility(invocation, ms, parameter, rowBounds, resultHandler, cacheKey, boundSql, executor, allTableName, dialect, dataPermission);
} catch (Exception e) {
log.error("数据权限拦截sql时异常:{}", e);
return invocation.proceed();
}
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Interceptor.super.plugin(target);
}
return target;
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}Current User Permission Retrieval – User permission data is stored in Redis. A Redis key is composed of userId + mapperMethod + requestUrl, and the value is a hash where each field is a table name mapping to its permission rule. The interceptor reads this key to decide whether to modify the SQL.
One‑Click Enable/Disable (Auto‑Configuration) – A custom Spring Boot starter provides the @EnableDataPermission annotation. Adding this annotation to the main application class automatically registers the interceptor, permission dialects, and related beans.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({DataPermissionAutoImport.class})
public @interface EnableDataPermission {} public class DataPermissionAutoImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"org.demo.common.permission.dialect.MySqlDataPermissionDialect",
"org.demo.common.permission.dialect.RedisDataPermissionDialect",
"org.demo.common.permission.dialect.RedisMysqlDataPermissionDialect",
"org.demo.common.permission.interceptor.DataPermissionsHandlerInterceptor",
"org.demo.common.permission.auto.ClassMethodGetValueBeanRegistry"
};
}
}Conclusion – This low‑intrusion design works well for small‑scale projects, but as the system grows, permission configuration can become cumbersome and may require more sophisticated management tools.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
