当前位置:网站首页>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 ?
边栏推荐
- 5. 無線體內納米網:十大“可行嗎?”問題
- Use of OLED screen
- Linear distance between two points of cesium
- Recyclerview GridLayout bisects the middle blank area
- 2110 summary of knowledge points and common problems in redis class
- Basic knowledge of lists
- Le lancement du jupyter ne répond pas après l'installation d'Anaconda
- Case ① | host security construction: best practice of 3 levels and 11 capabilities
- Initial experience of addresssanitizer Technology
- C language games - minesweeping
猜你喜欢
I've seen many tutorials, but I still can't write a program well. How can I break it?
B-jiege's tree (pressed tree DP)
Tencent byte and other big companies interview real questions summary, Netease architects in-depth explanation of Android Development
use. Net drives the OLED display of Jetson nano
Detailed introduction of distributed pressure measurement system VIII: basic introduction of akka actor model
[DSP] [Part 2] understand c6678 and create project
Rhcsa Road
Deep learning classification network -- zfnet
Enumeration gets values based on parameters
“罚点球”小游戏
随机推荐
02 basic introduction - data package expansion
[cloud lesson] EI lesson 47 Mrs offline data analysis - processing OBS data through Flink
Trends of "software" in robotics Engineering
Unity makes AB package
PowerPivot - DAX (first time)
What programming do children learn?
自定义限流注解
How to handle the timeout of golang
How to select several hard coded SQL rows- How to select several hardcoded SQL rows?
[DSP] [Part 2] understand c6678 and create project
OLED屏幕的使用
PHP online examination system version 4.0 source code computer + mobile terminal
Extraction rules and test objectives of performance test points
Review questions of anatomy and physiology · VIII blood system
使用.Net分析.Net达人挑战赛参与情况
[DSP] [Part 1] start DSP learning
数字三角形模型 AcWing 1018. 最低通行费
持续测试(CT)实战经验分享
Leetcode question 448 Find all missing numbers in the array
Pytest (3) - Test naming rules