Backend Development 9 min read

Design and Implementation of Data Permission Interception in Backend Services Using MyBatis and Spring Boot

The article explains how to design low‑intrusion data‑permission mechanisms for backend micro‑services by defining resources and value‑rules, storing permission data in Redis, intercepting MyBatis SQL execution, and enabling the whole solution with a Spring Boot auto‑configuration annotation.

Architect
Architect
Architect
Design and Implementation of Data Permission Interception in Backend Services Using MyBatis and Spring Boot

In small backend projects, developers often hard‑code data‑permission conditions directly in SQL, which works but leads to high coupling when services are split into micro‑modules.

The design aims for low or zero code intrusion and one‑click enable/disable by converting data permission into SQL interception that adds filter conditions based on defined resource and value rule concepts.

A resource maps a business field (e.g., createUserId ) to a permission scope; the value rule determines how the field is compared, such as = current user id or IN (list) .

Five basic value‑rule types are provided: self‑visible, department‑visible, department‑and‑sub‑visible, specified value, and custom, each translated into appropriate SQL fragments.

The detailed design adopts an RBAC model. When a SQL statement is executed, the interceptor obtains the current user ID, request URL, and mapper method name, builds a Redis key ( userId+mapperMethod+url ), and retrieves permission data stored as a hash where the key is the table name and the value contains field‑rule pairs.

SQL interception is illustrated with a MyBatis plugin. The interceptor implements @Intercepts on Executor.query , extracts the MappedStatement , parameters, and cache key, then delegates to ExecuteSqlContext.doResponsibility to append the permission conditions.

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
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);
    }
}

One‑click enable/disable is achieved through a Spring Boot auto‑configuration annotation @EnableDataPermission , which imports DataPermissionAutoImport . The selector registers the dialects and the interceptor, allowing any module to activate data permission simply by adding the annotation.

@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"
        };
    }
}

The approach is suitable for small‑scale projects, but as user volume and business complexity increase, manual configuration of data‑permission rules can become cumbersome, and additional mechanisms such as tagging may be required.

backendSpring BootMyBatisSQL InterceptorRBACData Permission
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.