java每日精进 7.29【框架数据权限详解】
数据权限控制执行步骤流程,以system模块的配置为例
1.加载springbean
1.YudaoDeptDataPermissionAutoConfiguration和 DataPermissionConfiguration
- 系统启动时,Spring Boot 扫描并加载所有标注了 @Configuration 或 @AutoConfiguration 的配置类。
- YudaoDeptDataPermissionAutoConfiguration 和 DataPermissionConfiguration 被加载。
/*** 基于部门的数据权限 AutoConfiguration* YudaoDeptDataPermissionAutoConfiguration(在满足条件时)创建 DeptDataPermissionRule Bean,* 并通过 DeptDataPermissionRuleCustomizer 配置部门相关规则。*/
@AutoConfiguration
@ConditionalOnClass(LoginUser.class)
@ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})
public class YudaoDeptDataPermissionAutoConfiguration {/*** 创建一个 DeptDataPermissionRule 对象,并将 permissionApi 作为参数传递给其构造函数。* 遍历 customizers 列表,调用每个 DeptDataPermissionRuleCustomizer 的 customize 方法* 对 rule 对象进行自定义配置。* 返回配置好的 DeptDataPermissionRule 对象。* @param permissionApi* @param customizers* @return*/@Beanpublic DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,List<DeptDataPermissionRuleCustomizer> customizers) {// 创建 DeptDataPermissionRule 对象DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);// 补全表配置customizers.forEach(customizer -> customizer.customize(rule));return rule;}
}
/*** system 模块的数据权限 Configuration*/
@Configuration(proxyBeanMethods = false)
public class DataPermissionConfiguration {@Beanpublic DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {return rule -> {// deptrule.addDeptColumn(AdminUserDO.class);rule.addDeptColumn(DeptDO.class, "id");// userrule.addUserColumn(AdminUserDO.class, "id");};}
}
DataPermissionConfiguration 类定义了 system 模块的权限规则
- AdminUserDO 表的 dept_id 字段用于部门过滤。
- DeptDO 表的 id 字段用于部门过滤。
- AdminUserDO 表的 id 字段用于用户过滤。
- YudaoDeptDataPermissionAutoConfiguration 创建 DeptDataPermissionRule 对象,注入 PermissionCommonApi。
- 获取所有 DeptDataPermissionRuleCustomizer(包括 DataPermissionConfiguration 提供的 sysDeptDataPermissionRuleCustomizer)
- 调用 customizer.customize(rule),执行:
- rule.addDeptColumn(AdminUserDO.class):添加 admin_user 表的 dept_id 字段。
- rule.addDeptColumn(DeptDO.class, "id"):添加 dept 表的 id 字段。
- rule.addUserColumn(AdminUserDO.class, "id"):添加 admin_user 表的 id 字段。
- 即system模块的DataPermissionConfiguration中的代码,添加类及其数据权限逻对应的字段
2.创建 DeptDataPermissionRule 对象:
- YudaoDeptDataPermissionAutoConfiguration 类,执行以下逻辑:
- 检查 LoginUser 类和 PermissionCommonApi、DeptDataPermissionRuleCustomizer 的 Bean 是否存在(通过 @ConditionalOnClass 和 @ConditionalOnBean)。
- 调用其 deptDataPermissionRule 方法,创建 DeptDataPermissionRule 对象:
- 构造 DeptDataPermissionRule 实例,注入 PermissionCommonApi(用于查询权限)。
- 获取所有 DeptDataPermissionRuleCustomizer 的 Bean(包括 DataPermissionConfiguration 提供的 sysDeptDataPermissionRuleCustomizer)。
3.应用权限规则配置:
- DataPermissionConfiguration 定义了 system 模块的权限规则,通过 sysDeptDataPermissionRuleCustomizer 方法:
- 返回一个 DeptDataPermissionRuleCustomizer 实例,调用其 customize 方法:
return rule -> {rule.addDeptColumn(AdminUserDO.class); // 配置 admin_user 表的 dept_id 字段rule.addDeptColumn(DeptDO.class, "id"); // 配置 dept 表的 id 字段rule.addUserColumn(AdminUserDO.class, "id"); // 配置 admin_user 表的 id 字段
};
- 执行 addDeptColumn(AdminUserDO.class):
- 调用 TableInfoHelper.getTableInfo(AdminUserDO.class),获取表名 admin_user。
- 执行 addDeptColumn("admin_user", "dept_id"),将 {"admin_user": "dept_id"} 存入 deptColumns,将 admin_user 添加到 TABLE_NAMES。
- 执行 addDeptColumn(DeptDO.class, "id"):
- 获取表名 dept,将 {"dept": "id"} 存入 deptColumns,将 dept 添加到 TABLE_NAMES。
- 执行 addUserColumn(AdminUserDO.class, "id"):
- 获取表名 admin_user,将 {"admin_user": "id"} 存入 userColumns,admin_user 已存在于 TABLE_NAMES,不重复添加。
- 执行 addDeptColumn(AdminUserDO.class):
- 结果:TABLE_NAMES = {"admin_user", "dept"},deptColumns = {"admin_user": "dept_id", "dept": "id"},userColumns = {"admin_user": "id"}。
public void addDeptColumn(Class<? extends BaseDO> entityClass) {addDeptColumn(entityClass, DEPT_COLUMN_NAME);}public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();addDeptColumn(tableName, columnName);}public void addDeptColumn(String tableName, String columnName) {deptColumns.put(tableName, columnName);TABLE_NAMES.add(tableName);}public void addUserColumn(Class<? extends BaseDO> entityClass) {addUserColumn(entityClass, USER_COLUMN_NAME);}public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();addUserColumn(tableName, columnName);}public void addUserColumn(String tableName, String columnName) {userColumns.put(tableName, columnName);TABLE_NAMES.add(tableName);}
4.注册 DeptDataPermissionRule:
- YudaoDeptDataPermissionAutoConfiguration 将配置好的 DeptDataPermissionRule 注册为 Spring Bean,供框架使用。
2. 用户登录
用户登录系统:
- 用户(假设 ID 为 123,类型为管理员)登录系统,框架通过 SecurityFrameworkUtils 创建 LoginUser 对象,存储用户 ID、类型等信息。
- LoginUser 包含一个上下文(Context),用于缓存权限数据(如 DeptDataPermissionRespDTO)。
3. 用户执行查询
用户发起查询:
- 用户执行 SQL 查询:SELECT * FROM admin_user。
- 框架(通过 MyBatis 拦截器或类似机制)拦截查询,解析 SQL,提取表名 admin_user 和表别名(这里为 null,因为没有 AS 别名)。
检查表是否需要权限控制:
- 框架调用 DeptDataPermissionRule.getTableNames(),返回 TABLE_NAMES = {"admin_user", "dept"}。
- 确认 admin_user 在 TABLE_NAMES 中,触发数据权限控制逻辑。
调用 getExpression 生成权限条件:
public Expression getExpression(String tableName, Alias tableAlias) {// 只有有登陆用户的情况下,才进行数据权限的处理LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();if (loginUser == null) {return null;}// 只有管理员类型的用户,才进行数据权限的处理if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {return null;}// 获得数据权限DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);// 从上下文中拿不到,则调用逻辑进行获取if (deptDataPermission == null) {deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());if (deptDataPermission == null) {log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",loginUser.getId(), tableName, tableAlias.getName()));}// 添加到上下文中,避免重复计算loginUser.setContext(CONTEXT_KEY, deptDataPermission);}// 情况一,如果是 ALL 可查看全部,则无需拼接条件if (deptDataPermission.getAll()) {return null;}// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限if (CollUtil.isEmpty(deptDataPermission.getDeptIds())&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空}// 情况三,拼接 Dept 和 User 的条件,最后组合Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());if (deptExpression == null && userExpression == null) {// TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
// throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
// loginUser.getId(), tableName, tableAlias.getName()));return EXPRESSION_NULL;}if (deptExpression == null) {return userExpression;}if (userExpression == null) {return deptExpression;}// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));}
- 框架调用 DeptDataPermissionRule.getExpression("admin_user", null):
- 获取登录用户:
- 执行 SecurityFrameworkUtils.getLoginUser(),返回 LoginUser(ID 为 123,类型为 UserTypeEnum.ADMIN)。
- 检查用户类型:
- 确认 loginUser.getUserType() == UserTypeEnum.ADMIN.getValue(),继续处理。
- 获取权限数据:
- 检查 LoginUser 的上下文,尝试获取 CONTEXT_KEY(DeptDataPermissionRule)对应的 DeptDataPermissionRespDTO。DeptDataPermissionRespDTO {
deptIds: [10, 20], // 可访问部门 10 和 20
self: true, // 可查看自己的数据
all: false // 不可查看所有数据
}
- 将权限数据存入上下文:loginUser.setContext(CONTEXT_KEY, deptDataPermission)。
- 检查权限:
- deptDataPermission.getAll() == false,需要生成条件。
- deptIds 不为空([10, 20]),self == true,需要生成部门和用户条件。
- 生成部门条件:
- 调用 buildDeptExpression("admin_user", null, [10, 20]):
- 检查 deptColumns,找到 admin_user: dept_id。
- deptIds 不为空,生成 InExpression:dept_id IN (10, 20)。
- 调用 buildDeptExpression("admin_user", null, [10, 20]):
- 生成用户条件:
- 调用 buildUserExpression("admin_user", null, true, 123):
- self == true,检查 userColumns,找到 admin_user: id。
- 生成 EqualsTo:id = 123。
- 调用 buildUserExpression("admin_user", null, true, 123):
- 合并条件:
- 部门和用户条件都存在,生成 OrExpression:(dept_id IN (10, 20) OR id = 123)。
- 用 ParenthesedExpressionList 包裹,返回最终条件。
- 获取登录用户:
- 框架调用 DeptDataPermissionRule.getExpression("admin_user", null):
- 修改 SQL 查询:
- 框架将生成的条件 (dept_id IN (10, 20) OR id = 123) 加入原 SQL:
- 原查询:SELECT * FROM admin_user。
- 修改后:SELECT * FROM admin_user WHERE (dept_id IN (10, 20) OR id = 123)。
- 框架将生成的条件 (dept_id IN (10, 20) OR id = 123) 加入原 SQL:
- 执行查询并返回结果:
- MyBatis 执行修改后的 SQL,只返回部门 ID 为 10 或 20 的记录,以及用户 ID 为 123 的记录。