基于 MyBatis 拦截器的动态数据权限控制方案
本文提出了一种基于MyBatis拦截器的动态数据权限控制方案。针对企业应用中不同角色对数据的差异化访问需求,该方案通过自定义注解标记需要权限控制的表、字段和方法,利用MyBatis拦截器在SQL执行前动态添加权限过滤条件。核心实现包括:1)注解模块定义权限控制元数据;2)拦截器模块解析注解并修改SQL;3)配置模块管理权限校验参数;4)用户信息模块提供权限依据。该方案相比传统业务层过滤方式,降低了
一、背景与痛点分析
1.1 真实业务场景
角色 | 数据访问范围 | 传统方案痛点 |
---|---|---|
普通员工 | 仅自己创建的任务 | 每个查询方法重复编写WHERE user_id=? |
部门经理 | 本部门所有任务 | 需关联部门表并动态拼接dept_id |
系统管理员 | 全量数据 | 权限逻辑与业务代码深度耦合 |
1.2 传统方案的致命缺陷
// 业务代码中混杂权限逻辑(反面示例)
public List<Task> listTasks() {
String sql = "SELECT * FROM tb_task";
if (currentUser.isStaff()) {
sql += " WHERE user_id = " + currentUser.getId(); // SQL注入风险!
} else if (currentUser.isManager()) {
sql += " JOIN dept ON..."; // 复杂度爆炸式增长
}
// ...
}
痛点归纳:
- 代码重复率高
- SQL注入风险
- 复杂查询难以维护
- 权限变更需修改业务代码
二、解决方案设计
2.1 核心思路
利用 MyBatis 的拦截器机制,在 SQL 执行之前对其进行拦截和修改,根据当前用户的权限信息动态地添加数据过滤条件。通过在实体类和方法上添加自定义注解,标记需要进行权限控制的表和字段,在拦截器中解析这些注解,并根据用户的权限信息生成相应的 SQL 过滤条件,从而实现对数据的动态权限控制。
2.2 核心架构
2.3、四大核心模板
模块 | 组件 | 职责说明 |
---|---|---|
注解模块 | @ ResourceCheck |
标记需权限控制的接口 |
@ResourceTableCheck |
标记需权限控制的实体类 | |
@ResourceFieldCheck |
定义字段过滤规则(如USER_ID , DEPT_PATH ) |
|
拦截器 | ResourceCodeInterceptor |
SQL解析与条件注入核心 |
配置模块 | PermissionProperties |
动态开关权限/配置白名单 |
上下文 | UserContextHolder |
基于ThreadLocal存储用户信息 |
三、代码实现细节
3.1 注解模块
通过三个核心注解实现权限规则的声明式配置:
// 标记需要权限控制的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceCheck {
}
// 标记需要权限控制的实体类(表)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceTableCheck {
}
// 标记需要权限控制的字段及过滤规则
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceFieldCheck {
ResoureFieldType code(); // 字段类型(用户ID、部门路径等)
SqlKeyword compare() default SqlKeyword.EQ; // 比较操作符
}
3.2 拦截器模块
执行流程:
3.2.1 权限校验拦截器
@Aspect
@Component
public class ResourceCheckAspect {
@Pointcut("@annotation(com.dynamic.data.annotation.ResourceCheck)")
public void annotationPointcut() {}
@Before("annotationPointcut()")
public void beforeAdvice() {
ResourceCheckHolder.set(true); // 标记需要权限校验
}
@After("annotationPointcut()")
public void afterAdvice() {
ResourceCheckHolder.clear(); // 清除标记
}
}
3.2.2 SQL 改写拦截器(核心)
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class ResourceCodeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
// 1. 检查是否需要权限校验
if (!Boolean.TRUE.equals(ResourceCheckHolder.get())) {
return invocation.proceed();
}
// 2. 获取原始SQL并解析
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
// 3. 动态添加权限过滤条件
String modifiedSql = addResourceCodeCondition(originalSql);
PluginUtils.mpBoundSql(boundSql).sql(modifiedSql);
return invocation.proceed();
}
private String addResourceCodeCondition(String originalSql) {
// 使用JSqlParser解析SQL
Statement statement = CCJSqlParserUtil.parse(originalSql);
// 处理不同类型的查询(SELECT/JOIN/SUBQUERY等)
if (statement instanceof Select) {
processSelect((Select) statement);
}
return statement.toString();
}
}
3.3 配置模块
3.3.1 PermissionProperties
配置类
@ConfigurationProperties(prefix = "permission")
public class PermissionProperties {
private boolean enabled = true; // 是否开启权限校验
private String prefix; // 拦截接口前缀
private List<String> excludePathPatterns;// 排除不校验的路径
private String userServiceUrl; // 用户服务地址
}
PermissionProperties
配置类用于配置权限校验的相关参数,如是否开启权限校验、排除不校验的路径、用户系统后端地址等。
3.3.2 WebMvcConfig
配置类
@Configuration
@EnableConfigurationProperties({PermissionProperties.class, ServerProperties.class})
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
@ConditionalOnMissingBean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor();
}
@Bean
@ConditionalOnMissingBean
public SpringContextUtil springContextUtil() {
return new SpringContextUtil();
}
@Bean
@ConditionalOnMissingBean
public ResourceCodeInterceptor resourceCodeInterceptor() {
return new ResourceCodeInterceptor();
}
@Bean
@ConditionalOnMissingBean
public ResourceCheckAspect resourceCheckAspect() {
return new ResourceCheckAspect();
}
@Autowired
private PermissionProperties permissionProperties;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加请求拦截器
if (permissionProperties.isEnabled()) {
registry.addInterceptor(requestInterceptor()).excludePathPatterns(permissionProperties.getExcludePathPatterns());
}
}
}
WebMvcConfig
配置类用于配置拦截器和 Spring 上下文工具类,将请求拦截器和 MyBatis 拦截器注册到 Spring 容器中,并根据配置决定是否启用请求拦截器。
在 spring.factories
文件中加入以下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.xxx.xxx.xxx.config.WebMvcConfig
将 WebMvcConfig
类声明为 Spring Boot 的自动配置类。当应用启动时,Spring Boot 会自动加载并实例化。
3.4 用户信息模块
线程安全的上下文管理,存储用户信息
public class UserContextHolder {
private static final ThreadLocal<UserInfoContext> USER_INFO_THREAD_LOCAL = new ThreadLocal<>();
public static void set(UserInfoContext userInfoContext) {
USER_INFO_THREAD_LOCAL.set(userInfoContext);
}
public static UserInfoContext get() {
return USER_INFO_THREAD_LOCAL.get();
}
public static void clear() {
USER_INFO_THREAD_LOCAL.remove();
}
}
四、代码调用示例
4.1 实体类定义
@ResourceTableCheck
@Getter
@Setter
@NoArgsConstructor
@TableName("tb_task")
public class TbTask {
// 基础字段...
@ResourceFieldCheck(code = ResoureFieldType.USER_ID)
@TableField(value = "user_id", keepGlobalFormat = true)
private Long userId;
@ResourceFieldCheck(code = ResoureFieldType.START_TIME, compare = SqlKeyword.GE)
@TableField(value = "create_time", keepGlobalFormat = true)
private Date createTime;
}
在实体类 TbTask
上添加 ResourceTableCheck
注解,表示该表需要进行权限控制。在 userId
和 createTime
字段上添加 ResourceFieldCheck
注解,指定了查询的字段类型和查询条件。
4.2 控制器定义
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private TaskService taskService;
// 无需权限校验的接口
@GetMapping("test1")
public void test1() {
taskService.list();
}
// 需要权限校验的接口
@ResourceCheck // 核心注解,开启权限控制
@GetMapping("test2")
public void test2() {
taskService.list();
}
}
4.3 SQL 转换效果
原始 SQL:
SELECT id,task_name,task_detail,user_id,create_time FROM tb_task
转换后 SQL
SELECT id, task_name, task_detail, user_id, create_time FROM tb_task WHERE tb_task.user_id = 1000 AND tb_task.create_time >= '2025-06-17 14:04:54'
五、方案优势与应用场景
5.1 核心优势
- 零侵入性:业务代码无需添加任何权限逻辑,DAO/Service 层保持纯净
- 声明式配置:通过注解即可完成权限规则定义,新增表只需添加注解
- 动态灵活性:
- 支持多种比较操作符(=, >, IN, BETWEEN 等)
- 支持多字段组合条件与复杂 SQL(JOIN / 子查询等)
- 安全增强:
- 自动拼接 WHERE 条件,防止 SQL 注入
- 线程安全的上下文管理
- 维护便捷:权限规则变更只需修改注解配置,无需修改业务代码
5.2 扩展应用场景
- 多租户隔离:通过租户 ID 字段实现数据隔离
- 数据时效控制:基于有效时间字段实现数据可见性控制
- 部门数据隔离:通过部门路径字段实现分级数据访问
- 混合权限策略:支持多维度权限组合(区域 + 销售级别等)
六、总结
本文提出的基于 MyBatis 拦截器的动态数据权限方案,通过巧妙的注解体系与 SQL 解析技术,实现了:
-
声明式权限配置:通过注解声明权限规则
-
自动化 SQL 改写:动态注入权限条件
-
上下文驱动:基于用户会话自动过滤
-
多维扩展能力:支持多种权限控制场景
相比传统硬编码方案,该方案减少权限相关代码量 80% 以上,极大提升系统的可维护性和安全性。
注:完整实现代码已开源:https://gitcode.com/weixin_44813108/dynamic-data-permission

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)