??一套完整的系統(tǒng)權(quán)限需要支持功能權(quán)限和數(shù)據(jù)權(quán)限,前面介紹了系統(tǒng)通過RBAC的權(quán)限模型來實(shí)現(xiàn)功能的權(quán)限控制,這里我們來介紹,通過擴(kuò)展Mybatis-Plus的插件DataPermissionInterceptor實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制。
??簡(jiǎn)單介紹一下,所謂功能權(quán)限,顧名思義是指用戶在系統(tǒng)中擁有對(duì)哪些功能操作的權(quán)限控制,而數(shù)據(jù)權(quán)限是指用戶在系統(tǒng)中能夠訪問哪些數(shù)據(jù)的權(quán)限控制,數(shù)據(jù)權(quán)限又分為行級(jí)數(shù)據(jù)權(quán)限和列級(jí)數(shù)據(jù)權(quán)限。
數(shù)據(jù)權(quán)限基本概念:
- 行級(jí)數(shù)據(jù)權(quán)限:以表結(jié)構(gòu)為描述對(duì)象,一個(gè)用戶擁有對(duì)哪些數(shù)據(jù)的權(quán)限,表示為對(duì)數(shù)據(jù)庫某個(gè)表整行的數(shù)據(jù)擁有權(quán)限,例如按部門區(qū)分,某一行數(shù)據(jù)屬于某個(gè)部門,某個(gè)用戶只對(duì)此部門的數(shù)據(jù)擁有權(quán)限,那么該用戶擁有此行的數(shù)據(jù)權(quán)限。
- 列級(jí)數(shù)據(jù)權(quán)限:以表結(jié)構(gòu)為描述對(duì)象,一個(gè)用戶可能只對(duì)某個(gè)表中的部分字段擁有權(quán)限,例如表中銀行卡、手機(jī)號(hào)等重要信息只有高級(jí)用戶能夠查詢,而一些基本信息,普通用戶就可以查詢,不同的用戶角色擁有的數(shù)據(jù)權(quán)限不一樣。
實(shí)現(xiàn)方式:
- 行級(jí)數(shù)據(jù)權(quán)限:
??對(duì)行級(jí)數(shù)據(jù)權(quán)限進(jìn)行細(xì)分,以角色為標(biāo)識(shí)的數(shù)據(jù)權(quán)限,分為:
??1、只能查看本人數(shù)據(jù);
??2、只能查看本部門數(shù)據(jù);
??3、只能查看本部門及子部門數(shù)據(jù);
??4、可以查看所有部門數(shù)據(jù);
??以用戶為標(biāo)識(shí)的數(shù)據(jù)權(quán)限,分為:
??5、同一功能角色權(quán)限擁有不同部門的數(shù)據(jù)權(quán)限;
??6、不同角色權(quán)限擁有不同部門的數(shù)據(jù)權(quán)限。
??第1/2/3/4類的實(shí)現(xiàn)方式需要在角色列表對(duì)角色進(jìn)行數(shù)據(jù)權(quán)限配置,針對(duì)某一接口該角色擁有哪種數(shù)據(jù)權(quán)限。
??第5類的實(shí)現(xiàn)方式,需要在用戶列表進(jìn)行配置,給用戶分配多個(gè)不同部門。
??第6類的實(shí)現(xiàn)方式比較復(fù)雜,目前有市面上的大多數(shù)解決方案是:
????1、在登錄時(shí),判斷用戶是否擁有多個(gè)部門,如果存在,那么首先讓用戶選擇其所在的部門,登錄后只對(duì)選擇的部門權(quán)限進(jìn)行操作;
????2、針對(duì)不同部門創(chuàng)建不同的用戶及角色,登錄時(shí),選擇對(duì)應(yīng)的賬號(hào)進(jìn)行登錄。
??個(gè)人因秉承復(fù)雜的系統(tǒng)簡(jiǎn)單化,盡量用低耦合的方式實(shí)現(xiàn)復(fù)雜功能的理念,更傾向于第二種方式,原因是:
??1、系統(tǒng)實(shí)現(xiàn)方面減少復(fù)雜度,越復(fù)雜的判斷,越容易出問題,不僅僅在開發(fā)過程中,還在于后續(xù)系統(tǒng)的擴(kuò)展和更新過程中。
??2、對(duì)于工作量方面的取舍,一個(gè)人擁有多個(gè)部門不同權(quán)限的方式屬于常用功能,但是并不普遍,也就是說在一家企業(yè)中,同一個(gè)用戶即是業(yè)務(wù)部門經(jīng)理,又是財(cái)務(wù)部門經(jīng)理的情況并不普遍,更多的是專人專職。這里要和第5類做好區(qū)分,比如你是業(yè)務(wù)部門經(jīng)理可能會(huì)管理多個(gè)部門,這種屬于權(quán)限一致,只是擁有多個(gè)部門權(quán)限,這屬于第5類。再比如一個(gè)總經(jīng)理,可能會(huì)看到所有的業(yè)務(wù)、財(cái)務(wù)數(shù)據(jù)這屬于第4類。
??所以這里不會(huì)采取用戶登錄后選擇部門的方式來判斷數(shù)據(jù)權(quán)限。
-
列級(jí)數(shù)據(jù)權(quán)限:
??列級(jí)數(shù)據(jù)權(quán)限的實(shí)現(xiàn)主要是針對(duì)某個(gè)角色能夠看到哪些字段,不存在針對(duì)某個(gè)用戶給他特定字段的情況,這種情況單獨(dú)建立一個(gè)角色即可,盡量采用類RBAC的方式來實(shí)現(xiàn),不要使用戶直接和數(shù)據(jù)權(quán)限關(guān)聯(lián)。列級(jí)數(shù)據(jù)權(quán)限除了要考慮后臺(tái)取數(shù)據(jù)的問題,還要考慮到在界面上展示時(shí),如果是一個(gè)表格,那么沒有權(quán)限的列需要根據(jù)數(shù)據(jù)權(quán)限來判斷是否展示。這里在配置界面就要考慮,在角色配置時(shí),需要分為行級(jí)數(shù)據(jù)權(quán)限和列級(jí)數(shù)據(jù)權(quán)限進(jìn)行不同的配置:行級(jí)數(shù)據(jù)權(quán)限應(yīng)該配置需要數(shù)據(jù)權(quán)限控制的接口,數(shù)據(jù)權(quán)限的類型(上面提到的1234);列級(jí)數(shù)據(jù)權(quán)限除了需要配置上面提到的之外,還需要配置可以訪問的字段或者排除訪問的字段。
數(shù)據(jù)權(quán)限
在資源管理配置資源關(guān)聯(lián)接口的數(shù)據(jù)權(quán)限規(guī)則(t_sys_data_permission_role),通過RBAC的方式用角色和用戶關(guān)聯(lián),在用戶管理配置用戶同角色的多個(gè)部門數(shù)據(jù)權(quán)限,用戶直接和部門關(guān)聯(lián)(t_sys_data_permission_user)。系統(tǒng)數(shù)據(jù)權(quán)限管理功能設(shè)計(jì)如下所示:

數(shù)據(jù)權(quán)限表設(shè)計(jì):
CREATE TABLE `t_sys_data_permission_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租戶id',
`user_id` bigint(20) NOT NULL COMMENT '用戶id',
`organization_id` bigint(20) NOT NULL COMMENT '機(jī)構(gòu)id',
`status` tinyint(2) NULL DEFAULT 1 COMMENT '狀態(tài) 0禁用,1 啟用,',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
`creator` bigint(20) NULL DEFAULT NULL COMMENT '創(chuàng)建者',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
`operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
`del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:刪除 0:不刪除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_data_permission_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租戶id',
`resource_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '功能權(quán)限id',
`data_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限名稱',
`data_mapper_function` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限對(duì)應(yīng)的mapper方法全路徑',
`data_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '需要做數(shù)據(jù)權(quán)限主表',
`data_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '需要做數(shù)據(jù)權(quán)限表的別名',
`data_column_exclude` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限需要排除的字段',
`data_column_include` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限需要保留的字段',
`inner_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限表,默認(rèn)t_sys_organization',
`inner_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '數(shù)據(jù)權(quán)限表的別名,默認(rèn)organization',
`data_permission_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '數(shù)據(jù)權(quán)限類型:1只能查看本人 2只能查看本部門 3只能查看本部門及子部門 4可以查看所有數(shù)據(jù)',
`custom_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自定義數(shù)據(jù)權(quán)限(增加 where條件)',
`status` tinyint(2) NOT NULL DEFAULT 1 COMMENT '狀態(tài) 0禁用,1 啟用,',
`comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '備注',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
`creator` bigint(20) NULL DEFAULT NULL COMMENT '創(chuàng)建者',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
`operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
`del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:刪除 0:不刪除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '數(shù)據(jù)權(quán)限配置表' ROW_FORMAT = DYNAMIC;
數(shù)據(jù)權(quán)限緩存(Redis)設(shè)計(jì):
- Redis Key:
多租戶模式:auth:tenant:data:permission:0(租戶):mapper_Mapper全路徑type數(shù)據(jù)權(quán)限類型
普通模式:auth:data:permission:mapper_Mapper全路徑type數(shù)據(jù)權(quán)限類型 - Redis Value:存放角色分配的DataPermissionEntity配置
??數(shù)據(jù)權(quán)限插件在組裝SQL時(shí),首先通過前綴匹配查詢mapper的statementId是否在緩存中,如果存在,那么取出當(dāng)前用戶的數(shù)據(jù)權(quán)限類型,組裝好帶有數(shù)據(jù)權(quán)限類型的DataPermission緩存Key,從緩存中取出數(shù)據(jù)權(quán)限配置。
在設(shè)計(jì)角色時(shí),除了需要給角色設(shè)置功能權(quán)限之外,還要設(shè)置數(shù)據(jù)權(quán)限類型,角色的數(shù)據(jù)權(quán)限類型只能單選(1只能查看本人 2只能查看本部門 3只能查看本部門及子部門 4可以查看所有數(shù)據(jù)5自定義)
代碼實(shí)現(xiàn):
- 因DataPermissionInterceptor默認(rèn)不支持修改selectItems,導(dǎo)致無法做到列級(jí)別的數(shù)據(jù)權(quán)限,所以這里自定義擴(kuò)展DataPermissionInterceptor,使其支持列級(jí)權(quán)限擴(kuò)展
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GitEggDataPermissionInterceptor extends DataPermissionInterceptor {
private GitEggDataPermissionHandler dataPermissionHandler;
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
if (!InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
}
}
protected void processSelect(Select select, int index, String sql, Object obj) {
SelectBody selectBody = select.getSelectBody();
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect)selectBody;
this.processDataPermission(plainSelect, (String)obj);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOperationList = (SetOperationList)selectBody;
List<SelectBody> selectBodyList = setOperationList.getSelects();
selectBodyList.forEach((s) -> {
PlainSelect plainSelect = (PlainSelect)s;
this.processDataPermission(plainSelect, (String)obj);
});
}
}
protected void processDataPermission(PlainSelect plainSelect, String whereSegment) {
this.dataPermissionHandler.processDataPermission(plainSelect, whereSegment);
}
}
- 自定義實(shí)現(xiàn)DataPermissionHandler數(shù)據(jù)權(quán)限控制
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class GitEggDataPermissionHandler implements DataPermissionHandler {
@Value(("${tenant.enable}"))
private Boolean enable;
/**
* 注解方式默認(rèn)關(guān)閉,這里只是說明一種實(shí)現(xiàn)方式,實(shí)際使用時(shí),使用配置的方式即可
*/
@Value(("${data-permission.annotation-enable}"))
private Boolean annotationEnable = false;
private final RedisTemplate redisTemplate;
public void processDataPermission(PlainSelect plainSelect, String mappedStatementId) {
try {
GitEggUser loginUser = GitEggAuthUtils.getCurrentUser();
// 1 當(dāng)有數(shù)據(jù)權(quán)限配置時(shí)才去判斷用戶是否有數(shù)據(jù)權(quán)限控制
if (ObjectUtils.isNotEmpty(loginUser) && CollectionUtils.isNotEmpty(loginUser.getDataPermissionTypeList())) {
// 1 根據(jù)系統(tǒng)配置的數(shù)據(jù)權(quán)限拼裝sql
StringBuffer statementSb = new StringBuffer();
if (enable)
{
statementSb.append(DataPermissionConstant.TENANT_DATA_PERMISSION_KEY).append(loginUser.getTenantId());
}
else
{
statementSb.append(DataPermissionConstant.DATA_PERMISSION_KEY);
}
String dataPermissionKey = statementSb.toString();
StringBuffer statementSbt = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER);
statementSbt.append(mappedStatementId).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE);
String mappedStatementIdKey = statementSbt.toString();
DataPermissionEntity dataPermissionEntity = null;
for (String dataPermissionType: loginUser.getDataPermissionTypeList())
{
String dataPermissionUserKey = mappedStatementIdKey + dataPermissionType;
dataPermissionEntity = (DataPermissionEntity) redisTemplate.boundHashOps(dataPermissionKey).get(dataPermissionUserKey);
if (ObjectUtils.isNotEmpty(dataPermissionEntity)) {
break;
}
}
// mappedStatementId是否有配置數(shù)據(jù)權(quán)限
if (ObjectUtils.isNotEmpty(dataPermissionEntity))
{
dataPermissionFilter(loginUser, dataPermissionEntity, plainSelect);
}
//默認(rèn)不開啟注解,因每次查詢都遍歷注解,影響性能,直接選擇使用配置的方式實(shí)現(xiàn)數(shù)據(jù)權(quán)限即可
else if(annotationEnable)
{
// 2 根據(jù)注解的數(shù)據(jù)權(quán)限拼裝sql
Class<?> clazz = Class.forName(mappedStatementId.substring(GitEggConstant.Number.ZERO, mappedStatementId.lastIndexOf(StringPool.DOT)));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + GitEggConstant.Number.ONE);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//當(dāng)有多個(gè)時(shí),這個(gè)方法可以獲取到
DataPermission[] annotations = method.getAnnotationsByType(DataPermission.class);
if (ObjectUtils.isNotEmpty(annotations) && method.getName().equals(methodName)) {
for (DataPermission dataPermission : annotations) {
String dataPermissionType = dataPermission.dataPermissionType();
for (String dataPermissionUser : loginUser.getDataPermissionTypeList()) {
if (ObjectUtils.isNotEmpty(dataPermission) && StringUtils.isNotEmpty(dataPermissionType)
&& dataPermissionUser.equals(dataPermissionType)) {
DataPermissionEntity dataPermissionEntityAnnotation = annotationToEntity(dataPermission);
dataPermissionFilter(loginUser, dataPermissionEntityAnnotation, plainSelect);
break;
}
}
}
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 構(gòu)建過濾條件
*
* @param user 當(dāng)前登錄用戶
* @param plainSelect plainSelect
* @return 構(gòu)建后查詢條件
*/
public static void dataPermissionFilter(GitEggUser user, DataPermissionEntity dataPermissionEntity, PlainSelect plainSelect) {
Expression expression = plainSelect.getWhere();
String dataPermissionType = dataPermissionEntity.getDataPermissionType();
String dataTableName = dataPermissionEntity.getDataTableName();
String dataTableAlias = dataPermissionEntity.getDataTableAlias();
String innerTableName = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableName()) ? dataPermissionEntity.getInnerTableName(): DataPermissionConstant.DATA_PERMISSION_TABLE_NAME;
String innerTableAlias = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableAlias()) ? dataPermissionEntity.getInnerTableAlias() : DataPermissionConstant.DATA_PERMISSION_TABLE_ALIAS_NAME;
List<String> organizationIdList = user.getOrganizationIdList();
// 列級(jí)數(shù)據(jù)權(quán)限
String dataColumnExclude = dataPermissionEntity.getDataColumnExclude();
String dataColumnInclude = dataPermissionEntity.getDataColumnInclude();
List<String> includeColumns = new ArrayList<>();
List<String> excludeColumns = new ArrayList<>();
// 只包含這幾個(gè)字段,也就是不是這幾個(gè)字段的,直接刪除
if (StringUtils.isNotEmpty(dataColumnInclude))
{
includeColumns = Arrays.asList(dataColumnInclude.split(StringPool.COMMA));
}
// 需要排除這幾個(gè)字段
if (StringUtils.isNotEmpty(dataColumnExclude))
{
excludeColumns = Arrays.asList(dataColumnExclude.split(StringPool.COMMA));
}
List<SelectItem> selectItems = plainSelect.getSelectItems();
List<SelectItem> removeItems = new ArrayList<>();
if (CollectionUtils.isNotEmpty(selectItems)
&& (CollectionUtils.isNotEmpty(includeColumns) || CollectionUtils.isNotEmpty(excludeColumns))) {
for (SelectItem selectItem : selectItems) {
// 暫不處理其他類型的selectItem
if (selectItem instanceof SelectExpressionItem) {
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
Alias alias = selectExpressionItem.getAlias();
if ((CollectionUtils.isNotEmpty(includeColumns) && !includeColumns.contains(alias.getName()))
|| (!CollectionUtils.isEmpty(excludeColumns) && excludeColumns.contains(alias.getName())))
{
removeItems.add(selectItem);
}
} else if (selectItem instanceof AllTableColumns) {
removeItems.add(selectItem);
}
}
if (CollectionUtils.isNotEmpty(removeItems))
{
selectItems.removeAll(removeItems);
plainSelect.setSelectItems(selectItems);
}
}
// 行級(jí)數(shù)據(jù)權(quán)限
// 查詢用戶機(jī)構(gòu)和子機(jī)構(gòu)的數(shù)據(jù),這里是使用where條件添加子查詢的方式來實(shí)現(xiàn)的,這樣的實(shí)現(xiàn)方式好處是不需要判斷Update,Insert還是Select,都是通用的,缺點(diǎn)是性能問題。
if (DataPermissionTypeEnum.DATA_PERMISSION_ORG_AND_CHILD.getLevel().equals(dataPermissionType)) {
// 如果是table的話,那么直接加inner,如果不是,那么直接在where條件里加子查詢
if (plainSelect.getFromItem() instanceof Table)
{
Table fromTable = (Table)plainSelect.getFromItem();
//數(shù)據(jù)主表
Table dataTable = null;
//inner數(shù)據(jù)權(quán)限表
Table innerTable = null;
if (fromTable.getName().equalsIgnoreCase(dataTableName))
{
dataTable = (Table)plainSelect.getFromItem();
}
// 如果是查詢,這里使用inner join關(guān)聯(lián)過濾,不使用子查詢,因?yàn)閖oin不需要建立臨時(shí)表,因此速度比子查詢快。
List<Join> joins = plainSelect.getJoins();
boolean hasPermissionTable = false;
if (CollectionUtils.isNotEmpty(joins)) {
Iterator joinsIterator = joins.iterator();
while(joinsIterator.hasNext()) {
Join join = (Join)joinsIterator.next();
// 判斷join里面是否存在t_sys_organization表,如果存在,那么直接使用,如果不存在則新增
FromItem rightItem = join.getRightItem();
if (rightItem instanceof Table) {
Table table = (Table)rightItem;
// 判斷需要inner的主表是否存在
if (null == dataTable && table.getName().equalsIgnoreCase(dataTableName))
{
dataTable = table;
}
// 判斷需要inner的表是否存在
if (table.getName().equalsIgnoreCase(innerTableName))
{
hasPermissionTable = true;
innerTable = table;
}
}
}
}
//如果沒有找到數(shù)據(jù)主表,那么直接拋出異常
if (null == dataTable)
{
throw new BusinessException("在SQL語句中沒有找到數(shù)據(jù)權(quán)限配置的主表,數(shù)據(jù)權(quán)限過濾失敗。");
}
//如果不存在這個(gè)table,那么新增一個(gè)innerjoin
if (!hasPermissionTable)
{
innerTable = new Table(innerTableName).withAlias(new Alias(innerTableAlias, false));
Join join = new Join();
join.withRightItem(innerTable);
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(dataTable, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));
equalsTo.setRightExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));
join.withOnExpression(equalsTo);
plainSelect.addJoins(join);
}
EqualsTo equalsToWhere = new EqualsTo();
equalsToWhere.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));
equalsToWhere.setRightExpression(new LongValue(user.getOrganizationId()));
Function function = new Function();
function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);
function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));
OrExpression orExpression = new OrExpression(equalsToWhere, function);
//判斷是否有數(shù)據(jù)權(quán)限,如果有數(shù)據(jù)權(quán)限配置,那么添加數(shù)據(jù)權(quán)限的機(jī)構(gòu)列表
if(CollectionUtils.isNotEmpty(organizationIdList))
{
for (String organizationId : organizationIdList)
{
EqualsTo equalsToPermission = new EqualsTo();
equalsToPermission.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));
equalsToPermission.setRightExpression(new LongValue(organizationId));
orExpression = new OrExpression(orExpression, equalsToPermission);
Function functionPermission = new Function();
functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);
functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(innerTable,DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));
orExpression = new OrExpression(orExpression, functionPermission);
}
}
expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(orExpression)) : orExpression;
plainSelect.setWhere(expression);
}
else
{
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(DataPermissionConstant.DATA_PERMISSION_ID))));
select.setFromItem(new Table(DataPermissionConstant.DATA_PERMISSION_TABLE_NAME));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID));
equalsTo.setRightExpression(new LongValue(user.getOrganizationId()));
Function function = new Function();
function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);
function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));
OrExpression orExpression = new OrExpression(equalsTo, function);
//判斷是否有數(shù)據(jù)權(quán)限,如果有數(shù)據(jù)權(quán)限配置,那么添加數(shù)據(jù)權(quán)限的機(jī)構(gòu)列表
if(CollectionUtils.isNotEmpty(organizationIdList))
{
for (String organizationId : organizationIdList)
{
EqualsTo equalsToPermission = new EqualsTo();
equalsToPermission.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID));
equalsToPermission.setRightExpression(new LongValue(organizationId));
orExpression = new OrExpression(orExpression, equalsToPermission);
Function functionPermission = new Function();
functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);
functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));
orExpression = new OrExpression(orExpression, functionPermission);
}
}
select.setWhere(orExpression);
subSelect.setSelectBody(select);
inExpression.setRightExpression(subSelect);
expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;
plainSelect.setWhere(expression);
}
}
// 只查詢用戶擁有機(jī)構(gòu)的數(shù)據(jù),不包含子機(jī)構(gòu)
else if (DataPermissionTypeEnum.DATA_PERMISSION_ORG.getLevel().equals(dataPermissionType)) {
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));
ExpressionList expressionList = new ExpressionList();
List<Expression> expressions = new ArrayList<>();
expressions.add(new LongValue(user.getOrganizationId()));
if(CollectionUtils.isNotEmpty(organizationIdList))
{
for (String organizationId : organizationIdList)
{
expressions.add(new LongValue(organizationId));
}
}
expressionList.setExpressions(expressions);
inExpression.setRightItemsList(expressionList);
expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;
plainSelect.setWhere(expression);
}
// 只能查詢個(gè)人數(shù)據(jù)
else if (DataPermissionTypeEnum.DATA_PERMISSION_SELF.getLevel().equals(dataPermissionType)) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_SELF));
equalsTo.setRightExpression(new StringValue(String.valueOf(user.getId())));
expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(equalsTo)) : equalsTo;
plainSelect.setWhere(expression);
}
//當(dāng)類型為查看所有數(shù)據(jù)時(shí),不處理
// if (DataPermissionTypeEnum.DATA_PERMISSION_ALL.getType().equals(dataPermissionType)) {
//
// }
// 自定義過濾語句
else if (DataPermissionTypeEnum.DATA_PERMISSION_CUSTOM.getLevel().equals(dataPermissionType)) {
String customExpression = dataPermissionEntity.getCustomExpression();
if (StringUtils.isEmpty(customExpression))
{
throw new BusinessException("沒有配置自定義表達(dá)式");
}
try {
Expression expressionCustom = CCJSqlParserUtil.parseCondExpression(customExpression);
expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(expressionCustom)) : expressionCustom;
plainSelect.setWhere(expression);
} catch (JSQLParserException e) {
throw new BusinessException("自定義表達(dá)式配置錯(cuò)誤");
}
}
}
/**
* 構(gòu)建Column
*
* @param dataTableAlias 表別名
* @param columnName 字段名稱
* @return 帶表別名字段
*/
public static Column buildColumn(String dataTableAlias, String columnName) {
if (StringUtils.isNotEmpty(dataTableAlias)) {
columnName = dataTableAlias + StringPool.DOT + columnName;
}
return new Column(columnName);
}
/**
* 注解轉(zhuǎn)為實(shí)體類
* @param annotation 注解
* @return 實(shí)體類
*/
public static DataPermissionEntity annotationToEntity(DataPermission annotation) {
DataPermissionEntity dataPermissionEntity = new DataPermissionEntity();
dataPermissionEntity.setDataPermissionType(annotation.dataPermissionType());
dataPermissionEntity.setDataColumnExclude(annotation.dataColumnExclude());
dataPermissionEntity.setDataColumnInclude(annotation.dataColumnInclude());
dataPermissionEntity.setDataTableName(annotation.dataTableName());
dataPermissionEntity.setDataTableAlias(annotation.dataTableAlias());
dataPermissionEntity.setInnerTableName(annotation.innerTableName());
dataPermissionEntity.setInnerTableAlias(annotation.innerTableAlias());
dataPermissionEntity.setCustomExpression(annotation.customExpression());
return dataPermissionEntity;
}
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
return null;
}
- 系統(tǒng)啟動(dòng)時(shí)初始化數(shù)據(jù)權(quán)限配置到Redis
@Override
public void initDataRolePermissions() {
List<DataPermissionRoleDTO> dataPermissionRoleList = dataPermissionRoleMapper.queryDataPermissionRoleListAll();
// 判斷是否開啟了租戶模式,如果開啟了,那么角色權(quán)限需要按租戶進(jìn)行分類存儲(chǔ)
if (enable) {
Map<Long, List<DataPermissionRoleDTO>> dataPermissionRoleListMap =
dataPermissionRoleList.stream().collect(Collectors.groupingBy(DataPermissionRoleDTO::getTenantId));
dataPermissionRoleListMap.forEach((key, value) -> {
// auth:tenant:data:permission:0
String redisKey = DataPermissionConstant.TENANT_DATA_PERMISSION_KEY + key;
redisTemplate.delete(redisKey);
addDataRolePermissions(redisKey, value);
});
} else {
redisTemplate.delete(DataPermissionConstant.DATA_PERMISSION_KEY);
// auth:data:permission
addDataRolePermissions(DataPermissionConstant.DATA_PERMISSION_KEY, dataPermissionRoleList);
}
}
private void addDataRolePermissions(String key, List<DataPermissionRoleDTO> dataPermissionRoleList) {
Map<String, DataPermissionEntity> dataPermissionMap = new TreeMap<>();
Optional.ofNullable(dataPermissionRoleList).orElse(new ArrayList<>()).forEach(dataPermissionRole -> {
String dataRolePermissionCache = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER)
.append(dataPermissionRole.getDataMapperFunction()).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE)
.append(dataPermissionRole.getDataPermissionType()).toString();
DataPermissionEntity dataPermissionEntity = BeanCopierUtils.copyByClass(dataPermissionRole, DataPermissionEntity.class);
dataPermissionMap.put(dataRolePermissionCache, dataPermissionEntity);
});
redisTemplate.boundHashOps(key).putAll(dataPermissionMap);
}
數(shù)據(jù)權(quán)限配置指南:

- 數(shù)據(jù)權(quán)限名稱:自定義一個(gè)名稱,方便查找和區(qū)分
- Mapper全路徑: Mapper路徑配置到具體方法名稱,例:com.gitegg.service.system.mapper.UserMapper.selectUserList
- 數(shù)據(jù)權(quán)限類型:
只能查看本人(實(shí)現(xiàn)原理是在查詢條件添加數(shù)據(jù)表的creator條件)
只能查看本部門 (實(shí)現(xiàn)原理是在查詢條件添加數(shù)據(jù)表的部門條件)
只能查看本部門及子部門 (實(shí)現(xiàn)原理是在查詢條件添加數(shù)據(jù)表的部門條件)
可以查看所有數(shù)據(jù)(不處理)
自定義(添加where子條件)
注解配置數(shù)據(jù)權(quán)限配置指南:
/**
* 查詢用戶列表
* @param page
* @param user
* @return
*/
@DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "3", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
@DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "2", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
@DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "1", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
Page<UserInfo> selectUserList(Page<UserInfo> page, @Param("user") QueryUserDTO user);
行級(jí)數(shù)據(jù)權(quán)限配置:
數(shù)據(jù)主表:主數(shù)據(jù)表,用于數(shù)據(jù)操作時(shí)的主表,例如SQL語句時(shí)的主表
數(shù)據(jù)主表別名:主數(shù)據(jù)表的別名,用于和數(shù)據(jù)權(quán)限表進(jìn)行inner join操作
數(shù)據(jù)權(quán)限表:用于inner join的數(shù)據(jù)權(quán)限表,主要用于使用ancestors字段查詢所有子組織機(jī)構(gòu)
數(shù)據(jù)權(quán)限表別名:用于和主數(shù)據(jù)表進(jìn)行inner join
列級(jí)數(shù)據(jù)權(quán)限配置:
排除的字段:配置沒有權(quán)限查看的字段,需要排除這些字段
保留的字段:配置有權(quán)限查看的字段,只保留這些字段
備注:
- 此數(shù)據(jù)權(quán)限設(shè)計(jì)較靈活,也較復(fù)雜,有些簡(jiǎn)單應(yīng)用場(chǎng)景的系統(tǒng)可能根本用不到,只需配置行級(jí)數(shù)據(jù)權(quán)限即可。
- Mybatis-Plus的插件DataPermissionInterceptor使用說明 https://gitee.com/baomidou/mybatis-plus/issues/I37I90
- update,insert邏輯說明:inner時(shí)只支持正常查詢,及inner查詢,不支持子查詢,update,insert,子查詢等直接使用添加子查詢的方式實(shí)現(xiàn)數(shù)據(jù)權(quán)限
- 還有在這里說明一下,在我們實(shí)際業(yè)務(wù)開發(fā)過程中,只能查看本人數(shù)據(jù)的數(shù)據(jù)權(quán)限,一般不會(huì)通過系統(tǒng)來配置,而是在業(yè)務(wù)代碼編寫過程中就 會(huì)實(shí)現(xiàn),比如查詢個(gè)人訂單接口,那么個(gè)人用戶id肯定是接口的入?yún)?,在接口被?qǐng)求的時(shí)候,只需要通過我們自定義的方法獲取到當(dāng)前登錄用戶,然后作為參數(shù)傳入即可。這種對(duì)于個(gè)人數(shù)據(jù)的數(shù)據(jù)權(quán)限,通過業(yè)務(wù)代碼來實(shí)現(xiàn)會(huì)更加方便和安全,且沒有太多的工作量,方便理解也容易擴(kuò)展。
GitEgg-Cloud是一款基于SpringCloud整合搭建的企業(yè)級(jí)微服務(wù)應(yīng)用開發(fā)框架,開源項(xiàng)目地址:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
歡迎感興趣的小伙伴Star支持一下。
