数据权限简单设计思路
现有权限控制分类
软件开发绕不过的两道基础集成功能,认证、鉴权,前者基于登录信息识别、校验、OAuth协议的授权认证、CAS、JWT等Token单点;后者采用基于Spring-Security、RBAC模型访问控制,表现为菜单、功能api访问的权限资源关联角色载体,角色关联用户的权限控制。
数据权限控制
数据权限属于权限一种,基于sql层面的字段可见、数据范围可见,常用场景,不同管理岗位同一张表不同字段可见性不同,华中区负责人看到当前区城市数据,单一城市只能看到当前市负责数据等
思考:能否通过接口返回值过滤?(分页、sql查询效率、后处理返回效率)
服务架构流程
权限字段的注册
数据权限资源的注册,需要鉴权的服务端启动的时候将自身链接数据库的所有表,所有字段的元数据信息注册到服务端,f_datacolumn用来存储字段权限信息,唯一键标识为:项目+库+表+字段名,字段类型,字段注释等辅助信息便于赋权规则时操作,时间类型赋值时间组件筛选范围,数值、字符串、字典值均可转换可视化赋值;f_rulecolumn存储权限字段赋予的规则信息;
【界面可视化操作,针对指定字段+字段逻辑操作符(and、or)+字段运算符(in范围、<>=!=值、contain包含)+字段匹配值(时间组件、数值、文本、字典值、内置变量{当前用户的机构、岗位等})】
f_columnrole关联字段和角色,以至角色关联用户。
权限字段授权
权限字段鉴权
mybatis提供的intercept插件拦截
@Intercepts({@Signature(method ="prepare", type =StatementHandler.class, args ={Connection.class})})publicclassFetchSqlInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocation invocation)throwsThrowable{//逻辑代码区return invocation.proceed();}@OverridepublicObjectplugin(Object target){//生成代理对象returnPlugin.wrap(target,this);}@OverridepublicvoidsetProperties(Properties properties){}}
实现 Interceptor 接口也就是实现intercept,plugin,setProperties这三个方法,其中
①
intercept
方法是我们拦截到对象后所进行操作的位置,也就是我们之后编写逻辑代码的位置。
②
plugin
方法,根据参数可以看出,该方法的作用是拦截我们需要拦截到的对象。
③
setProperties
方法,我们可以通过配置文件中进行properties配置,然后在该方法中读取到配置。
这三个方法的执行顺序: setProperties—>plugin—>intercept
@OverridepublicObjectintercept(Invocation invocation)throwsThrowable{StatementHandler handler =(StatementHandler)invocation.getTarget();//由于mappedStatement中有我们需要的方法id,但却是protected的,所以要通过反射获取MetaObject statementHandler =SystemMetaObject.forObject(handler);MappedStatement mappedStatement =(MappedStatement) statementHandler.getValue("delegate.mappedStatement");//获取sqlBoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();//增强sqlreturn invocation.proceed();}
实现插件接口后,添加至mybatis
@Configuration、@Bean
配置中
sqlSessionFactory.getConfiguration().addInterceptor(newFetchSqlInterceptor());
原始sql:
SELECT id,name,email,age FROM t_userinfo;
- 待鉴权客户端页面请求数据接口
- 持久层拦截使用JSqlParser工具解析原始sql获取表名
<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.0</version></dependency>
- 拦截调用服务端接口获取该用户拥有当前系统的字段规则(服务端根据用户code获取角色、角色关联的表字段、表字段关联的字段,根据字段规则:逻辑符+字段名+运算符+条件值,example:and age < 35),解析出字段规则表达式
- JSqlParser工具解析原始sql对应表的
where条件
+and 1=1
+字段规则表达式
- 执行增强sql,返回数据
加强后的sql:
SELECT id,name,email,age FROM t_userinfo WHERE1=1and age <35;
前端界面适配
动态渲染数据字段
根据数据鉴权返回的结果,数据范围上的限制,无需更改,字段限制上,动态适配渲染表单列即可。
开发注意事项及思考
权限合并
权限的叠加
用户和角色的关联关系是一对多的,任何如岗位上的不同权限,多部门分管的特殊情况,都可以使用多个角色分配、权限叠加使用,如此便涉及到多个角色的权限叠加结果问题,若是不同字段取多个角色关联的多个字段直接并集即可,不同角色包含同一个字段冲突导致的不同配置规则是需要逻辑层面的处理取得更大范围的逻辑上的权限为最终结果。
规则的叠加
- 针对多个角色叠加,导致的一个字段有多个规则是需要业务考虑的,简单的运算符上的age > 10 和 age > 25,最终取得规则一定是数据范围更大的 age > 10,<、=、!=逻辑一致;
- 字典值和内置变量,有逻辑上的范围大小,如当前所在的部门、上级部门、当前所处的岗位、上级领导岗位,则需梳理出字典值和内置变量中的排序字段,数值越小岗位或部门越大,根据此排序字段来合并规则条件,范围大者替代原则
- 若有字段配置冲突既等于又不等于,或大于大的数值小于小的数值,在角色分配叠加时候校验给出提醒是否是正确的权限需求分配方式,或冲突字段直接采用
or
逻辑符连接均保留(and (age > 25 or age < 10))
SQL拦截的通用可能性
- 基于Spring容器托管的持久层框架均可通过代理模式,截取执行的sql,例如本文的mybatis提供的插件实现(
JPA中Hibernate也提供了类似StatementInspector接口类
) - 不同类型数据库驱动如mysql提供了一个接口实现类
com.mysql.jdbc.StatementInterceptor(StatementInterceptorV2)
配置链接url添加参数jdbc.url=jdbc:mysql://127.0.0.1:3306/test?statementInterceptors=xxxStatementInterceptor
,除此之外原生jdbc等不同封装的持久层框架可能不容易获取到sql
版权归原作者 varCode 所有, 如有侵权,请联系我们删除。