当前位置:网站首页>7. Data permission annotation
7. Data permission annotation
2022-07-06 20:34:00 【Little happy】
Data access , An annotation is done !
Source code :https://github.com/1040580896/data_scope
Be careful
Place holder
sql.append(String.format(" OR %s.dept_id in(select rd.dept_id from sys_role_dept rd where rd.role_id=%d)", dataScope.deptAlias(), role.getRoleId()));
mysql The function in
mysql in find_in_set() Use of functions
https://www.cnblogs.com/xiaoxi/p/5889486.html
Set up the project
Will introduce Spring Security、Aop、mybatis-plus、mybatis Such dependence
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--aop Dependence -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Mistakes made
No import mybatis rely on , It throws an exception
Invalid bound statement (not found):
Code generator
@SpringBootTest
class DataScopeApplicationTests {
@Test
void contextLoads() {
FastAutoGenerator.create("jdbc:mysql:///tianchin?serverTimezone=Asia/Shanghai", "root", "th123456")
.globalConfig(builder -> {
builder.author("th") // Set the author
// .enableSwagger() // Turn on swagger Pattern
.fileOverride() // Overwrite generated file
.outputDir("/Users/xiaokaixin/Desktop/tianchi/demo/data_scope/src/main/java"); // Specify output directory
})
.packageConfig(builder -> {
builder.parent("com.th.ds") // Set the parent package name
// .moduleName("system") // Set the parent package module name
.pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/xiaokaixin/Desktop/tianchi/demo/data_scope/src/main/resources/mapper/")); // Set up mapperXml Path is generated
})
.strategyConfig(builder -> {
builder.addInclude("sys_dept","sys_role","sys_user") // Set the table name to be generated
.addTablePrefix("sys_"); // Set filter table prefix
})
.templateEngine(new FreemarkerTemplateEngine()) // Use Freemarker Engine templates , The default is Velocity Engine templates
.execute();
}
}
introduce SpringSecurity The details of the
user class
Rewrite his method
[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-HI5QLkK2-1653987529020)(img/image-20220531090230607.png)]

[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-2hBDROvs-1653987529022)(img/image-20220531090317844.png)]
UserServiceImpl
[ Failed to transfer the external chain picture , The origin station may have anti-theft chain mechanism , It is suggested to save the pictures and upload them directly (img-7KKKxoG0-1653987529023)(img/image-20220531090506537.png)]
1. Thought analysis
First of all, let's take a look at the idea of permission implementation here .
@DataScope
The content of annotation processing is called Data permission , That is, what data can you access after logging in . The traditional approach is to authenticate users according to the current id Or the role or authority and other information to query , But this method is more troublesome and troublesome , Every query needs to write a lot SQL, And these SQL There are a lot of similarities in , So we hope to deal with it in a unified way , Then it leads to @DataScope
annotation .
stay RuoYi-Vue In scaffolding , Divide the data permissions of users into five categories , They are as follows :
- 1: This indicates all data permissions , That is, this user can access all data after logging in , Generally speaking, only super administrators have this permission .
- 2: This indicates user-defined data permissions , User defined data permission means finding out which department data the user can operate according to the user's role , Take this as the basis for data query .
- 3: This indicates the data permission of the Department , This simple , This user can only query the data of this department .
- 4: This indicates the data authority of the Department and below , This means that users can query the data of this department and its subordinate departments .
- 5: This means that this user can only view their own data .
stay TienChin In this project , Data permission is basically based on the design of this scaffold , We just need to understand the implementation idea here , In the future, you can customize the data permission annotation at will .
2. Table structure analysis
Clear your mind , Let's look at the table structure .
Here are the following tables :
- sys_user: User table
- sys_role: Role table
- sys_dept: Departmental table
- sys_user_role: User role association table
- sys_role_dept: Role Department association table
There are some details in these tables. Let me sort them out with you . One by one .
There is one in the user table dept_id
Indicates the Department this user belongs to id, A user belongs to a department .
There is a field in the role table called data_scope
, Indicates the data permission corresponding to this role , The value is 1-5, Meaning is the meaning listed above , This is very important .
When designing the Department table , There is one ancestors Field , Through this field, you can easily query the sub departments of a department .
There is nothing to say about the last two correlation tables .
Okay , These are all analyzed , Let's take a look at the specific implementation .
3. Concrete realization
3.1 @DataScope
Let's first look at the definition of Data permission annotation :
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/** * Alias of department table */
public String deptAlias() default "";
/** * Alias of user table */
public String userAlias() default "";
}
There are two properties in this annotation , One is deptAlias and userAlias. Because the core idea of Data permission implementation is to implement SQL Dynamically append query conditions , Then dynamically added SQL We must consider the original SQL Department table alias and user table alias when defining . These two attributes are used to do this .
So the partners may also see , This @DataScope
It's not quite the same as our previous annotations , Other annotations customized in the past are less coupled with the business , This @DataScope
The coupling degree with the business is relatively high , You have to look at your business SQL What is the alias of department table and user table in , Then configure it on this annotation .
therefore ,@DataScope
Annotation is not a particularly flexible annotation , Let's take a learning attitude and understand his realization method .
3.2 Section analysis
The annotation is defined , Next is the section analysis .
@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 = "data_scope";
@Before("@annotation(dataScope)")
public void doBefore(JoinPoint jp, DataScope dataScope) {
clearDataScope(jp);
//1、 Get the current login user information
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user.getUserId() == 1L) {
// Description is super administrator , No permission filtering
return;
}
StringBuilder sql = new StringBuilder();
List<Role> roles = user.getRoles();
//select * from sys_dept d where d.del_flag='0' and (xxx OR xxx OR xxx)
//d.dept_id in(select rd.dept_id from sys_user_role ur,sys_role_dept rd where ur.user_id=2 and ur.role_id=rd.role_id) Representing one xxx
for (Role role : roles) {
// Get the data permission corresponding to the role
String ds = role.getDataScope();
if (DATA_SCOPE_ALL.equals(ds)) {
// If users can view all data permissions , There's nothing to do here
return;
} else if (DATA_SCOPE_CUSTOM.equals(ds)) {
// Customized data permissions , So based on User role to find the Department id
//todo Place holder
sql.append(String.format(" OR %s.dept_id in(select rd.dept_id from sys_role_dept rd where rd.role_id=%d)", dataScope.deptAlias(), role.getRoleId()));
} else if (DATA_SCOPE_DEPT.equals(ds)) {
sql.append(String.format(" OR %s.dept_id=%d", dataScope.deptAlias(), user.getDeptId()));
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(ds)) {
//todo find_in_set
sql.append(String.format(" OR %s.dept_id in(select dept_id from sys_dept where dept_id=%d or find_in_set(%d,`ancestors`))", dataScope.deptAlias(), user.getDeptId(), user.getDeptId()));
} else if (DATA_SCOPE_SELF.equals(ds)) {
String s = dataScope.userAlias();
if ("".equals(s)) {
// Data permission is limited to myself
sql.append(" OR 1=0");
} else {
sql.append(String.format(" OR %s.user_id=%d", dataScope.userAlias(), user.getUserId()));
}
}
}
// and( xxx or xxx or xxx)
Object arg = jp.getArgs()[0];
if (arg != null && arg instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) arg;
baseEntity.getParams().put(DATA_SCOPE, " AND ("+sql.substring(4)+")");
}
}
/** * If params There are already parameters in , Then delete the , prevent sql Inject * * @param jp */
private void clearDataScope(JoinPoint jp) {
Object arg = jp.getArgs()[0];
if (arg != null && arg instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) arg;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
- First of all, five different data permission types are defined , We have already introduced these five types , I won't go into that here .
- Next doBefore The method is a pre notification . because
@DataScope
The annotation is added to service Layer method , Therefore, pre notification is used here , Supplement for the implementation of the method SQL Parameters , This is the way of thinking : Annotated with data permission service The parameter of the layer method must be an object , And this object must inherit from BaseEntity,BaseEntity One of them Map Type of params attribute , If we need to service The implementation of layer method adds SQL, Then put the supplementary content in this params variable , Supplementary key That's what I saiddataScope
,value Is a SQL. stay doBefore Method first clearDataScope To clear params What is already in the variable , prevent SQL Inject ( Because of this params The content of can also be transmitted from the front end ); And then execute handleDataScope Method to filter data permissions . - stay handleDataScope In the method , Mainly to query the current user , And then call dataScopeFilter Method to filter data , This is the core method of filtering .
- Because a user may have multiple roles , So in dataScopeFilter Method must first traverse the role , Different roles have different data permissions , These different data permissions pass through OR Connected to a , Finally generated supplement SQL The format is similar to this
AND(xxx OR xxx OR xxx)
such , every last xxx Represents the filter conditions generated by a role . - The next step is to generate supplements according to different data permissions SQL 了 : If the data permission is 1, Then it generates SQL It's empty , I.e. inquiry SQL Do not add restrictions ; If the data permission is 2, Indicates custom data permission , At this time, the user's department is queried according to the user's role , Generate query restricted SQL; If the data permission is 3, It means that the user's Data permission is limited to his own department , Then pick out the Department to which the user belongs as the query limit ; If the data permission is 4, Indicates that the user's authority is his own department and his sub department , Then the user's department and its sub departments will be selected as the restricted query criteria ; If the data permission is 5, It means that the user's Data permission is limited to himself , That is, you can only view your own data , Then use the user's own id As the limiting condition of query . Last , And then put the generated SQL Deal with it a little , become
AND(xxx OR xxx OR xxx)
Format , This is a little bit easier , Is string interception + String splicing .
4. case analysis
Let's take a look @DataScope
Annotate three specific applications, and you will understand .
stay RuoYi-Vue In scaffolding , This annotation mainly has three usage scenarios :
- Inquiry Department .
- Query roles .
- Query the user .
Suppose I now take ry This user logs in , The role of this user is ordinary , The data permissions of ordinary roles are 2, That is, user-defined data permissions , Let's take a look at how this user queries data .
Let's look at .
4.1 Inquiry Department
First, the method of querying the Department is located in org.javaboy.tienchin.system.service.impl.SysDeptServiceImpl#selectDeptList
Location , The specific method is as follows :
@Override
@DataScope(deptAlias = "d")
public List<SysDept> selectDeptList(SysDept dept) {
return deptMapper.selectDeptList(dept);
}
This parameter SysDept Inherited from BaseEntity, and BaseEntity There is one of them. params attribute , We have already introduced this before , I won't repeat .
Let's take a look at this selectDeptList Method corresponding to SQL:
<sql id="selectDeptVo">
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time
from sys_dept d
</sql>
<select id="selectDeptList" parameterType="SysDept" resultMap="SysDeptResult">
<include refid="selectDeptVo"/>
where d.del_flag = '0'
<if test="deptId != null and deptId != 0">
AND dept_id = #{deptId}
</if>
<if test="parentId != null and parentId != 0">
AND parent_id = #{parentId}
</if>
<if test="deptName != null and deptName != ''">
AND dept_name like concat('%', #{deptName}, '%')
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<!-- Data range filtering -->
${params.dataScope}
order by d.parent_id, d.order_num
</select>
You can see , stay SQL There is a sentence at the end of ${params.dataScope}
, Is to put in DataScopeAspect Spliced in the section SQL Add in .
So this SQL The final form is similar to the following :
SELECT
d.dept_id,
d.parent_id,
d.ancestors,
d.dept_name,
d.order_num,
d.leader,
d.phone,
d.email,
d.STATUS,
d.del_flag,
d.create_by,
d.create_time
FROM
sys_dept d
WHERE
d.del_flag = '0'
AND ( d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 2 ) )
ORDER BY
d.parent_id,
d.order_num
** You can see , The last SQL after , Data filtering is realized ( Here is to filter according to user-defined data permissions ).** Then here is another detail , front SQL In defining , What is the table alias used , We are @DataScope
What is the alias specified in .
4.2 Query roles
First, the method of querying roles is located in org.javaboy.tienchin.system.service.impl.SysRoleServiceImpl#selectRoleList
Location , The specific method is as follows :
@Override
@DataScope(deptAlias = "d")
public List<SysRole> selectRoleList(SysRole role) {
return roleMapper.selectRoleList(role);
}
This parameter SysRole Inherited from BaseEntity, and BaseEntity There is one of them. params attribute , We have already introduced this before , I won't repeat .
Let's take a look at this selectRoleList Method corresponding to SQL:
<sql id="selectRoleVo">
select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
r.status, r.del_flag, r.create_time, r.remark
from sys_role r
left join sys_user_role ur on ur.role_id = r.role_id
left join sys_user u on u.user_id = ur.user_id
left join sys_dept d on u.dept_id = d.dept_id
</sql>
<select id="selectRoleList" parameterType="SysRole" resultMap="SysRoleResult">
<include refid="selectRoleVo"/>
where r.del_flag = '0'
<if test="roleId != null and roleId != 0">
AND r.role_id = #{roleId}
</if>
<if test="roleName != null and roleName != ''">
AND r.role_name like concat('%', #{roleName}, '%')
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="roleKey != null and roleKey != ''">
AND r.role_key like concat('%', #{roleKey}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- Start time Retrieval -->
and date_format(r.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- End time Retrieval -->
and date_format(r.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
</if>
<!-- Data range filtering -->
${params.dataScope}
order by r.role_sort
</select>
You can see , stay SQL There is a sentence at the end of ${params.dataScope}
, Is to put in DataScopeAspect Spliced in the section SQL Add in .
So this SQL The final form is similar to the following :
SELECT DISTINCT
r.role_id,
r.role_name,
r.role_key,
r.role_sort,
r.data_scope,
r.menu_check_strictly,
r.dept_check_strictly,
r.STATUS,
r.del_flag,
r.create_time,
r.remark
FROM
sys_role r
LEFT JOIN sys_user_role ur ON ur.role_id = r.role_id
LEFT JOIN sys_user u ON u.user_id = ur.user_id
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE
r.del_flag = '0'
AND ( d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 2 ) )
ORDER BY
r.role_sort
LIMIT ?
You can see , The last SQL after , Data filtering is realized ( Here is to filter according to user-defined data permissions ).
The logic of filtering is based on the Department the user belongs to id Find the user id, Then according to the user id Find the corresponding role id, Finally, return the queried roles .
In fact, I think the query department and query users conduct data filtering , This is easy to understand , What departments can the currently logged in user operate , Which users can operate , These are easy to understand , Which roles can be operated and how to understand ? Especially the above query SQL It's a big circle , Some of them may say , There is one in the system sys_role_dept
Do you have a watch ? This table is related to role information and department information , Take the user's Department directly id Come to this table to query the roles that users can operate id No, just go ? That's not the case ! Here I think you should understand it in this way : Department to which the user belongs this is the Department to which the user belongs , The Department that users can operate is the Department that can operate , There is no necessary connection between the two .sys_user In the table dept_id The field represents the Department to which this user belongs id, and sys_role_dept The table describes which departments a role can operate , It's different , Straighten this out , above SQL It's easy to understand .
4.3 Query the user
Finally, let's look at the query user .
The method of querying users is org.javaboy.tienchin.system.service.impl.SysUserServiceImpl#selectUserList
Location , Corresponding SQL as follows :
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, 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="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</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 != ''"><!-- Start time Retrieval -->
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 != ''"><!-- End time Retrieval -->
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>
<!-- Data range filtering -->
${params.dataScope}
</select>
This is easier , Query the user according to the department or the current user . Finally generated SQL Similar to the following ( This is the custom data permission , That is to find users according to their departments ):
SELECT
u.user_id,
u.dept_id,
u.nick_name,
u.user_name,
u.email,
u.avatar,
u.phonenumber,
u.PASSWORD,
u.sex,
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'
AND ( d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 2 ) )
LIMIT ?
边栏推荐
- Unity writes a timer tool to start timing from the whole point. The format is: 00:00:00
- Recyclerview GridLayout bisects the middle blank area
- JS implementation force deduction 71 question simplified path
- Case ① | host security construction: best practice of 3 levels and 11 capabilities
- Tencent byte and other big companies interview real questions summary, Netease architects in-depth explanation of Android Development
- 【每周一坑】输出三角形
- Discussion on beegfs high availability mode
- recyclerview gridlayout 平分中间空白区域
- I've seen many tutorials, but I still can't write a program well. How can I break it?
- Node.js: express + MySQL实现注册登录,身份认证
猜你喜欢
设计你的安全架构OKR
Build your own application based on Google's open source tensorflow object detection API video object recognition system (IV)
SQL injection 2
Comment faire une radio personnalisée
[network planning] Chapter 3 data link layer (3) channel division medium access control
Rhcsa Road
Maximum likelihood estimation and cross entropy loss
How does kubernetes support stateful applications through statefulset? (07)
Why do novices often fail to answer questions in the programming community, and even get ridiculed?
Error analysis ~csdn rebound shell error
随机推荐
解剖生理学复习题·VIII血液系统
Detailed introduction of distributed pressure measurement system VIII: basic introduction of akka actor model
PowerPivot - DAX (first time)
(work record) March 11, 2020 to March 15, 2021
深度学习分类网络 -- ZFNet
Tencent cloud database public cloud market ranks top 2!
Anaconda安裝後Jupyter launch 沒反應&網頁打開運行沒執行
In line elements are transformed into block level elements, and display transformation and implicit transformation
Cesium Click to draw a circle (dynamically draw a circle)
How does kubernetes support stateful applications through statefulset? (07)
02 基础入门-数据包拓展
“罚点球”小游戏
Le lancement du jupyter ne répond pas après l'installation d'Anaconda
持续测试(CT)实战经验分享
Quel genre de programmation les enfants apprennent - ils?
Intel 48 core new Xeon run point exposure: unexpected results against AMD zen3 in 3D cache
2022 refrigeration and air conditioning equipment installation and repair examination contents and new version of refrigeration and air conditioning equipment installation and repair examination quest
String length limit?
[weekly pit] positive integer factorization prime factor + [solution] calculate the sum of prime numbers within 100
Groovy basic syntax collation