当前位置:网站首页>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开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担.添加下方名片,即可获取全套学习资料哦
边栏推荐
- Configuration of routers and static routes
- Introduction to Network Layer Protocols
- el-progress implements different colors of the progress bar
- The cocos interview answers you are looking for are all here!
- Collection of error records (write down when you encounter them)
- 浏览器存储WebStorage
- numpy.random使用文档
- DevOps流程demo(实操记录)
- Billions of IT operations in the market, the product by strength to speak
- Nacos集群的搭建过程详解
猜你喜欢
随机推荐
错误记录集锦(遇到则记下)
原生JS带你了解数组方法实现及使用
el-progress implements different colors of the progress bar
config.js related configuration summary
vscode笔记
reduce()方法的学习和整理
产品学习资料
云计算基础-学习笔记
sql server duplicate values are counted after
Media query, rem mobile terminal adaptation
Problems encountered in installing Yolo3 target detection module in Autoware
Dry!Teach you to use industrial raspberries pie combining CODESYS configuration EtherCAT master station
七种让盒子水平垂直居中的方法
Teach you simple steps to achieve industrial raspberries pie properly installed RS232 USB drive
Mina断线重连
深入分析若依数据权限@datascope (注解+AOP+动态sql拼接) 【循序渐进,附分析过程】
LeetCode中常用语言的一些基本方法记录
NACOS配置中心设置配置文件
Mina的长连接和短连接
Mina disconnects and reconnects