告别硬编码!Spring Boot 3 + MyBatis-Plus 优雅实现数据权限控制
无处不在的数据权限之痛
在日常的企业级应用开发中,你是否经常遇到这样的场景:
- 权限代码泛滥:在每一个数据查询的 Service 或 Mapper 方法里,都充斥着 if-else 判断和手动拼接的 WHERE 条件,代码重复且难以维护。
- 安全风险隐匿:某个查询一旦忘记添加权限过滤,便可能导致敏感数据泄露,而这种疏漏在代码审查中极难被发现。
- 需求变更噩梦:当权限规则从“只能看自己的数据”变更为“可看本部门及下属部门数据”时,你需要满世界寻找并修改所有相关的SQL逻辑。
- 接口职责混乱:业务方法本应只关心核心业务逻辑,却被迫承载了大量数据访问控制的职责,违反了单一职责原则。
这些问题的核心在于:数据权限的控制逻辑,与业务逻辑高度耦合,并以“硬编码”的形式散落在系统的各个角落。
什么是数据权限?为何传统方案乏力?数据权限,是区别于功能权限(能否访问某个菜单或按钮)的更深层次的权限控制。它决定了一个用户在拥有功能访问权的前提下,能看到哪些范围的数据。常见的模型包括:
传统方案的弊端:
- 在业务层拼接参数:在 Service 层根据用户身份计算好数据ID列表,传递给 Mapper。这导致业务层臃肿,且无法应对复杂的动态SQL场景。
- 在Mapper层硬编码:在 MyBatis 的XML文件中,使用``标签或直接编写${}进行字符串替换。这种方式极不安全(存在SQL注入风险),且无法实现通用的、解耦的方案。
显然,我们需要一种非侵入式、集中化管理、动态灵活的解决方案。而 MyBatis-Plus 作为 MyBatis 的强力增强工具,其提供的数据权限插件(DataPermissionHandler) 正是为此而生。结合 Spring Boot 3 的现代化特性,我们可以构建出一个非常优雅的解决方案。
基于MyBatis-Plus插件实现动态数据过滤本方案的核心思想是:通过MyBatis-Plus插件,在SQL执行前,动态且自动地在查询语句上附加数据权限过滤条件。
第一步:环境准备与依赖引入确保你的项目是基于 Spring Boot 3.x 和 MyBatis-Plus 3.5.0+(较高版本对该功能支持更完善)。
第二步:定义数据权限注解
我们首先定义一个注解,用于在 Mapper 方法上声明此方法需要何种数据权限。这是实现精细化控制和解耦的关键。
/** * 数据权限注解 * 可标注在Mapper类或方法上,用于声明需要进行数据权限过滤 */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataPermission { /** * 数据权限字段名(默认表中表示部门ID的字段名为`dept_id`) */ String deptAlias() default "dept_id"; /** * 用户ID字段名(默认表中表示创建用户的字段名为`user_id`) */ String userAlias() default "user_id"; /** * 权限类型 */ DataScopeType type() default DataScopeType.ALL;}/** * 数据权限范围类型枚举 */public enum DataScopeType { ALL, // 所有数据 DEPT_AND_SUB, // 本部门及子部门 DEPT, // 本部门 SELF; // 仅本人数据}第三步:实现核心—自定义DataPermissionHandler
这是整个方案的“大脑”。它需要实现 MyBatis-Plus 的 DataPermissionHandler 接口,根据当前登录用户的信息和 @DataPermission 注解的配置,动态生成 WHERE 条件片段。
@Componentpublic class MyDataPermissionHandler implements DataPermissionHandler { @Autowired private UserContext userContext; // 假设已存在,用于获取当前登录用户信息 @Override public Expression getSqlSegment(Expression where, String mappedStatementId) { // 1. 获取当前执行的Mapper方法 MappedStatement ms = SqlHelper.getMappedStatement(mappedStatementId); Object annotation = getAnnotation(ms, DataPermission.class); if (annotation == null) { return where; // 没有注解,不做过滤 } DataPermission dataPermission = (DataPermission) annotation; // 2. 获取当前用户权限上下文 Long currentUserId = userContext.getCurrentUserId(); Long currentDeptId = userContext.getCurrentDeptId(); List第四步:配置插件并注入Spring
将我们自定义的 Handler 配置到 MyBatis-Plus 的拦截器链中。
@Configurationpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(MyDataPermissionHandler dataPermissionHandler) { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加数据权限插件 DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor(); dataPermissionInterceptor.setDataPermissionHandler(dataPermissionHandler); interceptor.addInnerInterceptor(dataPermissionInterceptor); // 可以继续添加其他插件,如分页插件、乐观锁插件 // interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }}第五步:在Mapper层进行声明式使用
现在,在需要进行数据权限控制的 Mapper 方法上,使用我们定义的 @DataPermission 注解即可,业务层代码无需任何改动。
@Mapperpublic interface OrderMapper extends BaseMapper
至此,一个优雅、解耦、声明式的数据权限控制方案就已搭建完成。 任何使用 @DataPermission 注解的查询,都会在运行时自动、无缝地注入对应的数据过滤条件。
总结彻底解耦:权限控制逻辑从业务代码中剥离,集中到 DataPermissionHandler 和注解中,符合设计原则。
声明式编程:通过在 Mapper 方法上添加注解即可启用控制,使用简单,意图清晰。
安全无侵入:基于MyBatis-Plus插件机制,在SQL引擎层进行安全的条件追加,避免了SQL注入风险。
灵活可扩展:DataPermissionHandler 中的逻辑可以根据你的组织架构和权限模型任意扩展,轻松支持更复杂的场景。
维护成本低:当权限规则变化时,通常只需修改 Handler 中的一处逻辑,极大提升了可维护性。
数据权限控制是构建安全、可靠企业应用的基石。与其继续在无穷尽的 if-else 和SQL拼接中挣扎,不如立即尝试将这套方案应用到你的 Spring Boot 3 项目中。
你是否在项目中遇到过更复杂的数据权限场景?例如,基于用户角色动态切换权限规则,或处理多租户(SaaS)场景下的数据隔离?欢迎在评论区分享你的经验和见解,让我们共同探讨更优的架构设计方案。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。
