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
allTableName = null;
Map
> 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.
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.