赞
踩
相关代码已上传:https://gitee.com/lakernote/easy-admin
已开源基于SpringBoot+Mybatisplus+Layui+SnakerFlow前后端分离轻量级工作流引擎的脚手架项目 easy-admin
为了解决用户和资源的操作关系, 让指定的用户,只能操作指定的资源。
基于资源,英文全程Access Control List
ACL是最早也是最基本的一种访问控制机制,它的原理非常简单:每一项资源,都配有一个权限列表,这个列表记录的就是哪些用户可以对这项资源执行CRUD中的那些操作。
当用户访问某资源时,会先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL是一种面向资源的访问控制模型,它的机制是围绕“资源”展开的。
优点:
实现简单,方便项目集成。
缺点:
需要维护大量的权限列表,在性能上有明显的缺陷。另外,对于拥有大量用户与众多资源的应用,管理访问控制列表本身就变成非常繁重的工作。
应用场景:在分享资源的场景,某个资源分享给某些人可用,例如分享我们的解说视频给群里的小伙伴。
权限列表示例:
resource_id | user_id | privilege |
---|---|---|
12 | 123 | 读/写/读写等 |
public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16
基于角色,英文全程Role Based Access Control
RBAC是把用户按角色进行归类,通过用户的角色来确定用户能否针对某项资源进行某项操作。
优点:
RBAC相对于ACL最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来,而用户与权限变成了间接关联。
RBAC模型使得访问控制,特别是对用户的授权管理变得非常简单和易于维护,因此有广泛的应用。
除两上述两种主要的模型之外,还有包括:基于属性的访问控制ABAC和基于策略的访问控制PBAC等等。
RBAC还有其他几种变种,但是核心一样。
数据模型
RBAC变种
除了数据权限我看目前的实现都是基于RBAC做个权限标识符集合做判断,这里我们只讨论数据权限的。
菜单按钮类
此图来着互联网
菜单、按钮、接口权限一般就是标识符集合。
常规的业务系统,数据粒度主要分为如下几种:
全部数据权限
部门数据权限:查看用户所在部门的数据。
部门及以下数据权限:查看用户所在部门及下属部门的数据。
本人数据权限:只能查看自己的数据。
自定义数据权限
有用这种sql改造型的,也有结合上面ACL模式实现的,具体情况请结合业务选择,这里也是讲下SQL改造型的。
原理是在业务sql实际执行前改造原sql,也即是在原sql查询条件加上create_by = xxx
,create_dept_id = xxx
等过滤条件。
例如原业务sql如下:
select * from leave where leave_day > #{day}
如果当前用户只有本人数据权限则改造后sql如下:
select * from leave where leave_day > #{day} and create_by = #{currentUserId}
使用内置的DataPermissionInterceptor
拦截器。
第一步:实现自己的DataPermissionHandler
/** * 这种只能处理查询 不能处理 cud * 且不支持别名 */ @Slf4j public class LakerDataPermissionHandler implements DataPermissionHandler { @Override public Expression getSqlSegment(Expression where, String mappedStatementId) { List<String> split = StrUtil.split(mappedStatementId, '.'); ... try { switch (dataPower.get().getDataFilterType()) { // 查看全部 case ALL: return where; // 查看本人所在组织机构以及下属机构 case DEPT_SETS: // 创建IN 表达式 // 创建IN范围的元素集合 Set<Long> deptIds = userInfoAndPowers.getDeptIds(); // 把集合转变为JSQLParser需要的元素列表 ItemsList itemsList = new ExpressionList(deptIds.stream().map(LongValue::new).collect(Collectors.toList())); InExpression inExpression = new InExpression(new Column("create_dept_id"), itemsList); AndExpression andExpression = new AndExpression(where, inExpression); log.info(WHERE, andExpression); return andExpression; // 查看当前部门的数据 case DEPT: // = 表达式 // dept_id = deptId EqualsTo equalsTo = new EqualsTo(); equalsTo.setLeftExpression(new Column("create_dept_id")); equalsTo.setRightExpression(new LongValue(userInfoAndPowers.getDeptId())); // 创建 AND 表达式 拼接Where 和 = 表达式 // WHERE xxx AND dept_id = 3 AndExpression deptAndExpression = new AndExpression(where, equalsTo); log.info(WHERE, deptAndExpression); return deptAndExpression; // 查看自己的数据 case SELF: // create_by = userId EqualsTo selfEqualsTo = new EqualsTo(); selfEqualsTo.setLeftExpression(new Column("create_by")); selfEqualsTo.setRightExpression(new LongValue(userInfoAndPowers.getUserId())); AndExpression selfAndExpression = new AndExpression(where, selfEqualsTo); log.info(WHERE, selfAndExpression); return selfAndExpression; case DIY: return new AndExpression(where, new StringValue(userInfoAndPowers.getSql())); default: break; } } catch (Exception e) { log.error("LakerDataPermissionHandler.err", e); } ... return where; } }
第二步:把数据权限拦截器加入到拦截器链路中
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加数据权限插件
DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();
LakerDataPermissionHandler lakerDataPermissionHandler = new LakerDataPermissionHandler();
// 添加自定义的数据权限处理器
dataPermissionInterceptor.setDataPermissionHandler(lakerDataPermissionHandler);
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
但是这种实现方式,只支持数据查询权限,不支持数据删除权限、数据修改权限等。
第一步:实现自定义的权限拦截器,默认的权限拦截器只有beforeQuery()
public class LakerDataPermissionV2Interceptor extends JsqlParserSupport implements InnerInterceptor { private LakerV2DataPermissionHandler dataPermissionHandler = new LakerV2DataPermissionHandler(); @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) return; PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); } @Override protected void processSelect(Select select, int index, String sql, Object obj) { PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect, (String) obj); if (null != sqlSegment) { plainSelect.setWhere(sqlSegment); } } @Override public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); parserSingle(mpBs.sql(), ms.getId()); } ... }
第二步:实现自己的DataPermissionHandler
public class LakerV2DataPermissionHandler { public static final String WHERE = " where {}"; @SneakyThrows public Expression getSqlSegment(PlainSelect plainSelect, String mappedStatementId) { // 获取原SQL Where 条件表达式 Expression where = plainSelect.getWhere(); // 获取sql语句的from 主表 Table fromItem = (Table) plainSelect.getFromItem(); // 有别名用别名,无别名用表名,防止字段冲突报错 Alias fromItemAlias = fromItem.getAlias(); String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName(); ... switch (dataPower.get().getDataFilterType()) { // 查看自己的数据 case SELF: // create_by = userId EqualsTo selfEqualsTo = new EqualsTo(); selfEqualsTo.setLeftExpression(new Column(mainTableName + ".create_by")); selfEqualsTo.setRightExpression(new LongValue(userInfoAndPowers.getUserId())); AndExpression selfAndExpression = new AndExpression(where, selfEqualsTo); log.info(WHERE, selfAndExpression); return selfAndExpression; case DIY: return new AndExpression(where, new StringValue(userInfoAndPowers.getSql())); default: break; ... } }
第三步:把数据权限拦截器加入到拦截器链路中
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// V2版本
LakerDataPermissionV2Interceptor dataPermissionInterceptor = new LakerDataPermissionV2Interceptor();
interceptor.addInnerInterceptor(dataPermissionInterceptor);
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
原始sql
SELECT * FROM ext_leave WHERE (leave_day >= ?)
数据权限过滤sql
SELECT * FROM ext_leave WHERE (leave_day >= ?) AND ext_leave.create_by = 16
原始sql
SELECT * FROM ext_leave WHERE (leave_day >= ?) LIMIT ?
数据权限过滤sql
SELECT * FROM ext_leave WHERE (leave_day >= ?) AND ext_leave.create_by = 16 LIMIT ?
原始sql
SELECT l.*, u.nick_name uNickName, d.dept_name uDeptName
FROM ext_leave l
LEFT JOIN sys_user u ON u.user_id = l.create_by
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
WHERE (l.leave_day >= ?) ORDER BY l.create_time DESC LIMIT ?
数据权限过滤sql
SELECT l.*, u.nick_name uNickName, d.dept_name uDeptName
FROM ext_leave l
LEFT JOIN sys_user u ON u.user_id = l.create_by
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
WHERE (l.leave_day >= ?) AND l.create_by = 16 ORDER BY l.create_time DESC LIMIT ?
别名情况也能自动识别
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。