当前位置:网站首页>In-depth analysis if according to data authority @datascope (annotation + AOP + dynamic sql splicing) [step by step, with analysis process]
In-depth analysis if according to data authority @datascope (annotation + AOP + dynamic sql splicing) [step by step, with analysis process]
2022-08-05 06:45:00 【User Nickname 23】
The author is currently working hard to analyze the open source project Ruoyi framework,Today I saw the part of Ruoyi's control of data permissions,自定义注解+AOP+动态SQL的注入,看的我是眼花缭乱,Then I seriously reviewed the entire implementation process again,不由得感叹一句,若依YYDS~~
简单猜测
Except what we usually know 路由权限(i.e. outside the access rights to the interface),in daily production development,We should also have access to the data.
In the framework of Zoe,Data permissions are controlled through the attribute of the data scope in the role.
对应实体类:
深入分析
One user definitely is have a role,It must also belong to a department.
Here we use the user when querying the user,即 selectUserListTake the data authorization as an example for analysis.
If you are accessing data rights,持久层(Mapper层)中对数据进行处理,Data is filtered based on the permissions of the user role.
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
</if>
<if test="loginName != null and loginName != ''">
AND u.login_name like concat('%', #{loginName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
</if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE FIND_IN_SET (#{deptId},ancestors) ))
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
</select>
We can see the penultimate row${params.dataScope}It is to filter the data range
其中,params指的是parameterType="SysUser"传来的参数 SysUse的一个属性,Then this propertydataScope属性.
思考:既然params.dataScopeEmbed here via placeholder,Then he must be onesql语句.我们返回到sysUser实体类中,发现sysUser中并没有params 这个属性.
这里我们可以看到SysUser继承了BaseEntity,果然我们在BaseEntityfound in this class params 这个属性
public class BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;
/** 搜索值 */
private String searchValue;
/** 创建者 */
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新者 */
private String updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 备注 */
private String remark;
/** 请求参数 */
private Map<String, Object> params;
public String getSearchValue()
{
return searchValue;
}
public void setSearchValue(String searchValue)
{
this.searchValue = searchValue;
}
public String getCreateBy()
{
return createBy;
}
public void setCreateBy(String createBy)
{
this.createBy = createBy;
}
public Date getCreateTime()
{
return createTime;
}
public void setCreateTime(Date createTime)
{
this.createTime = createTime;
}
public String getUpdateBy()
{
return updateBy;
}
public void setUpdateBy(String updateBy)
{
this.updateBy = updateBy;
}
public Date getUpdateTime()
{
return updateTime;
}
public void setUpdateTime(Date updateTime)
{
this.updateTime = updateTime;
}
public String getRemark()
{
return remark;
}
public void setRemark(String remark)
{
this.remark = remark;
}
public Map<String, Object> getParams()
{
if (params == null)
{
params = new HashMap<>();
}
return params;
}
public void setParams(Map<String, Object> params)
{
this.params = params;
}
}
到这里 我们已经知道有 params这个属性的存在,And can be sure params.dataScope 这是一个sql语句. So our next step is to determine thissql语句是什么时候注入的,怎么注入的?
高阶玩法
We can find that Zoe has customized an annotation @DataScope
/**
* 数据权限过滤注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
}
We continue to explore how this annotation is implemented
我们可以发现在 SysUserServiceImpl ServiceFound in the business layer implementation class @DataScope 的使用
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
Let's observe nowDataScopeAspect 这个切面类,I have annotated the specific implementation steps next to it
/**
* 数据过滤处理
*
* @author ruoyi
*/
@Aspect
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
@Before("@annotation(controllerDataScope)") // 这个controllerDataScope是一个形参
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
// 获取当前的用户
SysUser currentUser = ShiroUtils.getSysUser();
if (currentUser != null)
{
// 如果是超级管理员,则不过滤数据
if (!currentUser.isAdmin())
{
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
{
StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles()) //获取当前用户的角色
{
String dataScope = role.getDataScope(); //Get data permissions for a role
// If the data permission for this role is 全部数据权限
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
//自定义数据权限
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
}
//本部门数据权限
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
//本部门及以下数据权限
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
}
//仅本人数据权限
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
//将完成好的sqlStatements are placed in the entity class params的 dataScope属性中 这个属性是一个Map
}
}
}
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/
private void clearDataScope(final JoinPoint joinPoint)
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
!!!!完结撒花!!!
If you understand the process,I believe you can also in the database sys_role_deptThe meaning of this table,I didn't even know what this meant at the time.In fact, this table is to store those The role of the custom data permission and its corresponding department data permission.
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在.深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小.自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前.因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担.添加下方名片,即可获取全套学习资料哦
边栏推荐
- LaTeX使用frame制作PPT图片没有标号
- Complete mysql offline installation in 5 minutes
- 花花省V5淘宝客APP源码无加密社交电商自营商城系统带抖音接口
- LeetCode practice and self-comprehension record (1)
- Drools规则引擎快速入门(一)
- 请问下通过flink sql读取hologres 的两张表的 binlog,然后如何进行join?
- Difference between link and @improt
- BIO, NIO, AIO practical study notes (easy to understand theory)
- Chengyun Technology was invited to attend the 2022 Alibaba Cloud Partner Conference and won the "Gathering Strength and Going Far" Award
- sql server 重复值在后面计数
猜你喜欢
scikit-image图像处理笔记
D45_Camera assembly Camera
农场游戏果园系统+牧场养殖系统+广告联盟模式流量主游戏小程序APP V1
Problems encountered in installing Yolo3 target detection module in Autoware
LeetCode practice and self-comprehension record (1)
NACOS配置中心设置配置文件
Configuration of routers and static routes
config.js相关配置汇总
Chengyun Technology was invited to attend the 2022 Alibaba Cloud Partner Conference and won the "Gathering Strength and Going Far" Award
DevOps流程demo(实操记录)
随机推荐
Transformer interprets and predicts instance records in detail
[ingress]-ingress使用tcp端口暴露服务
程序员应该这样理解I/O
【考研结束第一天,过于空虚,想对自己进行总结一下】
Drools规则引擎快速入门(一)
Network Protocol Fundamentals - Study Notes
初识网页与浏览器
config.js相关配置汇总
Mina断线重连
Met with the browser page
The method of using ROS1 bag under ROS2
Successful indie developers deal with failure & imposters
LeetCode练习及自己理解记录(1)
Autoware--Beike Tianhui rfans lidar uses the camera & lidar joint calibration file to verify the fusion effect of point cloud images
el-autocomplete use
Does flink cdc currently support Gauss database sources?
Wireshark packet capture and common filtering methods
ROS2下使用ROS1 bag的方法
js判断文字是否超过区域
H5开发调试-Fiddler手机抓包