通用数据权限设计方案
一、数据权限设计初衷近期在做数据中台的项目,主要包括元数据管理,质量规则,数据服务,数据导出等多个业务模块,目前该中台只是供内部使用,但随着业务的正式上线,必然会在公网进行访问。因此,如何控制每个用户只能访问自己能够看到的数据内容至关重要,这就存在用户数据权限的设计问题。二、数据权限的设计方案条件(1)满足不同的业务授权方式统一化,通用且方便扩展;(2)与业务模块完全解耦,减少代码的侵入,提高权限
一、数据权限设计初衷
近期在做数据中台的项目,主要包括元数据管理,质量规则,数据服务,数据导出等多个业务模块,目前该中台只是供内部使用,但随着业务的正式上线,必然会在公网进行访问。因此,如何控制每个用户只能访问自己能够看到的数据内容至关重要,这就存在用户数据权限的设计问题。
二、前提条件
(1)满足不同的业务授权方式统一化,通用且方便扩展;
(2)与业务模块完全解耦,减少代码的侵入,提高权限系统的复用性;
(3)业务方不需要关心授权流程及授权资源回显问题。要保证不同的资源类型,在授权时,能够自动从后台获取需要授权的资源列表信息进行授权。
(4)授权系统易用,可维护性高。
三、数据权限设计整体架构
本次设计是基于用户-角色-资源体系构建的通用数据权限系统。通过将角色与资源绑定,再将用户和角色绑定,从而达到用户与资源的关联关系。
在资源模块中,提供统一资源授权接口和统一资源鉴权接口。统一资源授权接口主要是用来提供不同资源在授权时web页面回显的问题,后台主要是通过反射的方式来实现的。统一资源鉴权接口主要是提供gaia用户的鉴权操作,通过对用户所拥有的资源类型和角色的判断,从而给中台返回该用户能够访问的资源id集合,中台返回具体的资源内容信息。
四、数据权限核心代码实现
4.1依赖springboot对bean的管理,达到接口实现的自动扫描操作。
public abstract class ClassUtil<T> implements InitializingBean, ApplicationContextAware {
protected ApplicationContext applicationContext;
private final Map<String, T> map = new HashMap<>();
/**
* 设置spring上下文
* @param applicationContext spring上下文
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
setImplementedBeanMap(map, Objects.nonNull(getTypeClass()) ? getTypeClass() : getTClass());
}
/**
* 设置接口实现类Map
* @param map 存放接口实现类Map
* @param type 接口类
*/
protected <E> void setImplementedBeanMap(Map<String, E> map, Class<E> type) {
Map<String, E> beanMap = applicationContext.getBeansOfType(type);
for (E storageType : beanMap.values()) {
map.put(storageType.getClass().getSimpleName(), storageType);
}
}
/**
* 获取实现类集合
*/
protected Map<String, T> getUseMap() {
return map;
}
@SuppressWarnings("unchecked")
private Class<T> getTClass() {
return (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
/**
* 获取接口类Class
*/
protected abstract Class<T> getTypeClass();
}
4.2 统一授权接口实现类
public interface PrivilegeResourceTemplate {
Pagination<PrivilegeResourceEntity> showResourceList(String className, String keyword, Integer pageNum, Integer pageSize);
}
前端调用统一接口
@Service
public class PrivilegeBaseServiceImpl extends ClassUtil<PrivilegeResourceTemplate> implements PrivilegeBaseService {
@Override
protected Class<PrivilegeResourceTemplate> getTypeClass() {
return PrivilegeResourceTemplate.class;
}
/**
* 授权资源列表
* 统一授权资源展示接口
* @param className 具体实现类
* @param keyword 关键字
* @param pageNum 分页参数
* @param pageSize 分页参数
* @return
*/
@Override
public Pagination<PrivilegeResourceEntity> resourceLists(String className, String keyword, Integer pageNum, Integer pageSize) {
Map<String, PrivilegeResourceTemplate> map = getUseMap();
return map.get(className).showResourceList(className, keyword, pageNum, pageSize);
}
/**
* 获取实现类列表
* 通过该方法,可以获取接口的具体实现类有哪些
* @return
*/
@Override
public Set<String> getClassImpl() {
Map<String, PrivilegeResourceTemplate> map = getUseMap();
Set<String> classImpls = map.keySet();
return classImpls;
}
}
4.3统一鉴权接口,用到了redis,如果不需要,可以去掉。
public interface PrivilegeCommonAuthorize {
List<Object> getUserRefResourceIds(String resourceType);
}
@Service
public class PrivilegeCommonAuthorizeImpl implements PrivilegeCommonAuthorize {
@Autowired
private RedisUtil redisUtil;
@Autowired
private PrivilegeRoleRefUserMapper privilegeRoleRefUserMapper;
@Override
public List<Object> getUserRefResourceIds(String resourceType) {
if (StringUtils.isEmpty(resourceType)) {
return null;
}
String employeeNo = "涉及业务,已去掉,有需要自己添加逻辑";
String key = CommonConstants.USER_KEY + employeeNo;
Set<Object> roleIds = redisUtil.sGet(key);
/**
* 如果redis中没有,则去数据库查询,并存入redis
*/
if (roleIds.isEmpty()) {
roleIds = privilegeRoleRefUserMapper.queryRoleRefUserByUserId(employeeNo);
}
if (!roleIds.isEmpty()) {
redisUtil.sSet(key, roleIds.toArray());
} else {
throw new CustomException("该用户未被授权,请先授权后再进行操作");
}
Set<String> roleIdsSet = new HashSet<>();
roleIds.forEach(roleId -> {
String str = resourceType + CommonConstants.SPLITOR + roleId;
String roleKey = CommonConstants.ROLE_KEY + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
roleIdsSet.add(roleKey);
});
return redisUtil.multiGet(roleIdsSet);
}
}
五、数据权限实现流程
新增资源时的配置页面:
5.1 开发人员在开发完成之后,需要在该页面配置资源类型和该资源的具体实现类(后台实现PrivilegeResourceTemplate 接口的具体实现类)。
5.2 在授权时,前端选择资源类型,后台接收到资源类型的值,从数据库查询该资源类型具体的实现类,然后通过反射,获取该资源类型的授权资源内容回显在web页面进行授权。
5.3 鉴权时,用户登录中台,通过网关获取到用户的信息,然后从redis(mysql)中查询该用户(对应的资源类型所拥有)的角色信息,然后通过角色信息,查询该角色下挂载的资源内容(资源id),返回给中台具体接口进行回显拦截,从而达到权限控制的目的。
六、数据库表结构设计
1、用户表
CREATE TABLE `privilege_user` (
`privilege_user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(16) DEFAULT NULL COMMENT '用户名',
`employ_id` varchar(10) DEFAULT NULL COMMENT '员工编码',
`account` varchar(12) DEFAULT NULL COMMENT 'gaia账号',
`business_descr` varchar(32) DEFAULT NULL COMMENT '所属公司',
`creator` varchar(16) DEFAULT NULL COMMENT '创建人姓名',
`user_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '用户状态,0标识正常,1表示关闭',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`privilege_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4
2、角色表
CREATE TABLE `privilege_role` (
`privilege_role_id` varchar(18) NOT NULL COMMENT '主键,角色id',
`role_name` varchar(32) DEFAULT NULL COMMENT '角色名',
`role_descr` varchar(256) DEFAULT NULL COMMENT '角色描述',
`added_by_name` varchar(16) DEFAULT NULL COMMENT '创建人姓名',
`role_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '角色状态,0表示正常,1表示关闭',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`privilege_role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
3、角色用户关联表
CREATE TABLE `privilege_role_ref_user` (
`privilege_role_ref_user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`role_id` varchar(18) DEFAULT NULL COMMENT '角色id',
`user_id` varchar(10) DEFAULT NULL COMMENT '用户id',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`privilege_role_ref_user_id`),
KEY `index_name` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
4、角色和资源关联表
CREATE TABLE `privilege_role_ref_resource` (
`privilege_role_ref_resource_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`role_id` varchar(18) DEFAULT NULL COMMENT '角色id',
`resource_id` bigint(20) DEFAULT NULL COMMENT '资源模型id',
`resource_type` varchar(32) DEFAULT NULL COMMENT '资源类型',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`privilege_role_ref_resource_id`),
KEY `index_name` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4
5、资源配置表
CREATE TABLE `privilege_resource_class` (
`resource_reflex_id` bigint(20) NOT NULL AUTO_INCREMENT,
`resource_type` varchar(32) DEFAULT NULL COMMENT '资源类型',
`resource_name` varchar(255) DEFAULT NULL COMMENT '资源名',
`class_name` varchar(128) DEFAULT NULL COMMENT '类名',
`creator` varchar(16) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`resource_reflex_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
七、总结
1、该数据权限设计系统,完全解耦业务代码,通用性强,可扩展。
2、表结构清晰,模块明朗,容易维护;
3、设计简单,方便使用。
更多推荐
所有评论(0)