Article directory
Permissions are a relatively common basic function in daily systems. Login is required for systems with permission modules 用户能够操作哪些资源,不能够操作哪些资源
. With the help of the permission module, we can effectively control the specific operations performed by people with different identities in the system. It can be said that a mature back-end system is inseparable from a relatively complete permission management system.
So the goal of the permission control system is:管理用户行为,保护系统功能。
So how to perform permission control?
- Define resources
- Create permissions
- Creating a Role
- Manage users
- Establish an association
1. RBAC permission model
1.Composition of RBAC
- In the RBAC model, there are three basic components, namely:
用户、角色和权限
- User : Each user is identified by a unique UID and is granted different roles
- Role : Different roles have different permissions
- Permission : access rights
- User-role mapping : the mapping relationship between users and roles
- Role-permission mapping : mapping between roles and permissions
Role permission relationship
- Permission → Resource: One-way many-to-many. One permission can contain multiple resources, and one resource can be assigned to multiple different permissions.
- Role → Permission: One-way many-to-many. One role can contain multiple permissions, and one permission can be assigned to multiple different roles.
- User → Role: Two-way many-to-many. One role can contain multiple users, and one user can hold multiple positions.
For example, administrators and ordinary users are granted different permissions. Ordinary users can only modify and view personal information, but cannot create users or freeze users. Administrators can do all operations because they are granted all permissions.
2.RBAC model classification
2.1.Basic model RBAC0
RBAC0 is the foundation , and many systems can build permission models based only on RBAC0.
- In this model, we
把权限赋予角色,再把角色赋予用户
. Users and roles, roles and permissions are多对多
all related. The permissions a user has are equal to the sum of the permissions held by all his roles.
To give an example:
- For example, if we make an enterprise management product, we can
抽象出几个角色
, for example, sales managers, financial managers, marketing managers , etc., and then把权限分配给这些角色,再把角色赋予用户
. In this way, whether it is assigning permissions or modifying permissions in the future , you only need to modify them用户和角色的关系,或角色和权限的关系
, which is more flexible and convenient. - In addition, if a user has multiple roles, and Mr. Wang is responsible for both the sales department and the marketing department , then Mr. Wang can be given the roles of
2个角色
sales manager and marketing manager, so that he has both这两个角色的所有权限
.
2.2. Role hierarchical model RBAC1
RBAC1 is based on RBAC0 在角色中引入了 "继承" 的概念
. The simple understanding is, 给角色可以分成几个等级,每个等级权限不同,从而实现更细粒度的权限管理
.
To give an example:
- Based on the previous example of RBAC0, we also found that the sales manager of a company may be divided into several levels. For example, in addition to the sales manager, there is also a deputy sales manager, and the deputy sales manager only has part of the authority of the sales manager. At this time, we can use the RBAC1 grading model to divide the role of sales manager into multiple levels, and assign a lower level to the deputy sales manager.
2.3. Role restriction model RBAC2
RBAC2 is also built on RBAC0
the foundation, but adds some restrictions on users, roles and permissions.
- These restrictions can be divided into two categories, namely
静态职责分离SSD
(Static Separation of Duty) and动态职责分离DSD
(Dynamic Separation of Duty). The specific restrictions are as follows:
To give an example:
- Still based on the previous example of RBAC0, we found that some roles are
互斥
needed- For example, if a user is assigned the role of sales manager, he cannot be assigned the role of financial manager, otherwise he
录入合同
can审核合同
- For another example, some companies attach great importance to role upgrades. If a salesperson wants to be upgraded to a sales manager, he must first be upgraded to a sales supervisor. At this time, it should be adopted
先决条件限制
.
- For another example, some companies attach great importance to role upgrades. If a salesperson wants to be upgraded to a sales manager, he must first be upgraded to a sales supervisor. At this time, it should be adopted
- For example, if a user is assigned the role of sales manager, he cannot be assigned the role of financial manager, otherwise he
2.4. Unified model RBAC3
RBAC3 is RBAC1和RBAC2的合集
, so RBAC3 has both ** 角色分层
and OK 增加各种限制
. **
3.RBAC0 model core table structure
3.1.Table structure design
- From the above entity correspondence analysis, the permission table design is divided into the following five basic table structures: user table (t_user), role table (t_role), t_user_role (user role table), resource table (t_module), permission table (t_permission) , the table structure relationship is as follows:
t_user 用户表
主键 id int(11) 自增 主键id
user_name varchar(20) 非空 用户名
user_pwd varchar(100) 非空 用户密码
true_name varchar(20) 可空 真实姓名
email varchar(30) 可空 邮箱
phone varchar(20) 可空 电话
is_valid int(4) 可空 有效状态
create_date datetime 可空 创建时间
update_date datetime 可空 更新时间
t_role 角色表
主键 id int(11) 自增 主键id
role_name varchar(20) 非空 角色名
role_remarker varchar(100) 可空 角色备注
is_valid int(4) 可空 有效状态
create_date datetime 可空 创建时间
t_user_role 用户角色表
主键 id int(11) 自增 主键id
user_id int(4) 非空 用户id
role_id int(4) 角色id 角色id
create_date datetime 可空 创建时间
update_date datetime 可空 更新时间
t_module 资源表
主键 id int(11) 自增 资源id
module_name varchar(20) 可空 资源名
module_style varchar(100) 可空 资源样式
url varchar(20) 可空 资源url地址
parent_id int(11) 非空 上级资源id
parent_opt_value varchar(20) 非空 上级资源权限码
grade int(11) 非空 层级
opt_value varchar(30) 可空 权限码
orders int(11) 非空 排序号
is_valid int(4) 可空 有效状态
create_date datetime 可空 创建时间
update_date datetime 可空 更新时间
t_permission 权限表
主键 id int(11) 自增 主键id
role_id int(11) 非空 角色id
module_id int(11) 非空 资源id
acl_value varchar(20) 非空 权限码
create_date datetime 可空 创建时间
update_date datetime 可空 更新时间
3.2. Module division
It can be seen from the table structure design: there are three main tables (t_user, t_role, t_module), which are divided into three major modules in terms of function implementation:
User Management
- Maintenance of basic user information
- User role assignment
role management
- Maintenance of basic role information
- Role authorization (assign menus that can be operated by roles)
- Role authentication (verify the permissions owned by the role)
Resource management
- Resource information maintenance
- Menu output dynamic control
4. RBAC-based extension—user group
-
Based on the RBAC model, it can also be appropriately extended to make it more suitable for our products. For example
增加用户组概念
, directly to the user户组分配角色,再把用户加入用户
group. In this way, in addition to having their own permissions, the user also has all the permissions of the user group to which he belongs.
To give an example: -
For example, we can combine one
部门看成一个用户组
, such as the sales department and the finance department, and then give这个部门直接赋予角色,使部门拥有部门权限,这样这个部门的所有用户都有了部门权限。
the user group concept to more conveniently authorize group users without affecting the role permissions that the users already have.
2. ABAC permission model (explanation based on Java)
1.What is ABAC
ABAC(Attribute Base Access Control)
- Based
属性
on permission control, which is different from the common way of associating users with permissions in some way, ABAC makes authorization judgments by dynamically calculating one or a group of attributes to determine whether certain conditions are met (simple logic can be written). - Attributes are generally divided into four categories:
用户属性(如用户年龄)
,环境属性(如当前时间)
,操作属性(如读取)和对象属性
, so in theory, very flexible permission control can be achieved and can meet almost all types of needs.
2. ABAC conditional judgment control
Based on ABAC access control requirements 要动态计算实体的属性、操作类型、相关的环境来控制是否有对操作对象的权限
, the flexibility, versatility, and ease of use of conditional judgment need to be considered during design. Users only need to configure authorization through the web page, which requires reducing hard-coding and making the logic simple and universal. , then this needs to satisfy some operators to achieve.
type | operator |
---|---|
arithmetic operators | +, -, *, /, %, ^, div, mod |
Relational operators | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
Logical Operators | and, or, not, &&, |
condition | ?: |
- For example: Users only need to configure the condition of user.age > 20 to obtain specific permissions.
3. Expression Language (EL)
As mentioned in the previous section, if you need to parse a certain condition, you need an expression language. Using the expression language, you can easily access the attributes in the object, or perform various mathematical operations, conditional judgments, etc.
As: Spring Framework的@Value
Annotations and MyBatis<if test=“”>
// 相信很多 Java boy都使用过的吧
@Value("A?B:C")
private String A;
<select id = "XXX">
<if test="user != null">
XXXX
</if>
</select
Therefore 此ABAC的的核心就是Expression Language(EL)
, although the demonstration is used by Java as a demonstration, other programming languages have their own EL frameworks.
java EL framework list
-
spring-expression
-
OGNL
-
MVEL
-
JBoss EL
4.ABAC practice
4.1. Database design
# 用户表
DROP TABLE IF EXISTS USER;
CREATE TABLE USER (
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id) COMMENT '用户表'
);
# 用户边缘数据
DROP TABLE IF EXISTS user_contribution;
CREATE TABLE user_contribution (
id BIGINT(20) NOT NULL COMMENT '主键ID',
user_id BIGINT(20) NOT NULL COMMENT '用户表ID',
repository VARCHAR(100) NOT NULL COMMENT '仓库',
PRIMARY KEY (id) COMMENT '用户边缘数据'
);
# 权限表
DROP TABLE IF EXISTS permission;
CREATE TABLE permission (
id BIGINT(20) NOT NULL COMMENT '主键ID',
permission VARCHAR(100) NOT NULL COMMENT '权限名称',
PRIMARY KEY (id) COMMENT '权限表'
);
# abac表达式表
DROP TABLE IF EXISTS abac;
CREATE TABLE abac (
id BIGINT(20) NOT NULL COMMENT '主键ID',
expression VARCHAR(100) NOT NULL COMMENT 'abac表达式',
PRIMARY KEY (id) COMMENT 'abac表达式表'
);
# abac表和权限表的关联表, o2m
DROP TABLE IF EXISTS abac_permission;
CREATE TABLE abac_permission (
id BIGINT(20) NOT NULL COMMENT '主键ID',
abac_id BIGINT(20) NOT NULL COMMENT 'abac表ID',
permission_id BIGINT(20) NOT NULL COMMENT 'permission表ID',
PRIMARY KEY (id) COMMENT 'abac表和权限表的关联表, o2m'
);
#插入用户数据
DELETE FROM USER;
INSERT INTO USER (id, NAME, age, email)
VALUES (1, '魏昌进', 26, '[email protected]'),
(2, 'test', 1, '[email protected]'),
(3, 'admin', 1, '[email protected]');
#插入用户边缘数据
DELETE FROM user_contribution;
INSERT INTO user_contribution (id, user_id, repository)
VALUES (1, 1, 'galaxy-sea/spring-cloud-apisix'),
(2, 2, 'spring-cloud/spring-cloud-commons'),
(3, 2, 'spring-cloud/spring-cloud-openfeign'),
(4, 2, 'alibaba/spring-cloud-alibaba'),
(5, 2, 'Tencent/spring-cloud-tencent'),
(6, 2, 'apache/apisix-docker');
#插入权限数据
DELETE FROM permission;
INSERT INTO permission (id, permission)
VALUES (1, 'github:pr:merge'),
(2, 'github:pr:close'),
(3, 'github:pr:open'),
(4, 'github:pr:comment');
#插入abac表达式数据
DELETE FROM abac;
INSERT INTO abac (id, expression)
VALUES (1, 'contributions.contains(''galaxy-sea/spring-cloud-apisix'')'),
(2, 'name == ''admin'''),
(3, 'metadata.get(''ip'') == ''192.168.0.1''');
#插入abac表达式-权限映射关系数据
DELETE FROM abac_permission;
INSERT INTO abac_permission (id, abac_id, permission_id)
VALUES (1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 2, 3),
(5, 2, 4),
(6, 3, 1),
(7, 3, 2),
(8, 3, 3),
(9, 3, 4);
4.2.Introducing dependencies
This chapter only implements the principles of ABAC and will not do Spring Security和 Apache Shiro
any integration
frame
- Java 8
- Spring Boot 2.x
- MyBatis Plus 3.5.x
- MySQL 8.0
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>abac</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-abac</name>
<description>springboot-abac</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<!-- security权限依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.3. Modify configuration
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
#数据库名字自己定义
url: jdbc:mysql://127.0.0.1:3306/abac_test?serverTimezone=UTC&useUnicode=true&charaterEncoding=utf-8&useSSL=false
validation-query: SELECT 1
mybatis-plus:
#配置 Maaper xml文件所在路径
mapper-locations: classpath*:/mapper/**/*.xml
#配置映射类所在的包名
type-aliases-package: com.example.entity
server:
port: 8090
4.4.CRUD code
entiy
@Data
public class Abac {
private Long id;
private String expression;
/**
* expression对应的permissions列表
*/
@TableField(exist = false)
private List<String> permissions;
}
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
/** 用户提交过仓库 */
@TableField(exist = false)
private List<String> contributions = new ArrayList<>();
/** 存放一些乱七八糟的数据,当然contributions字段也放在这里 */
@TableField(exist = false)
private Map<String, Object> metadata = new HashMap<>();
}
layer
@Mapper
public interface AbacDao extends BaseMapper<Abac> {
/** 获取abacId关联权限 */
@Select("SELECT p.permission\n" +
"FROM abac_permission ap LEFT JOIN permission p ON p.id = ap.permission_id\n" +
"WHERE ap.abac_id = #{abacId}")
List<String> selectPermissions(Long abacId);
}
@Mapper
public interface UserDao extends BaseMapper<User> {
/** 获取用户的仓库信息 */
@Select("SELECT repository FROM user_contribution WHERE user_id = #{userId}")
List<String> selectRepository(@Param("userId") Long userId);
}
service layer
@Service
@RequiredArgsConstructor
public class AbacService {
private final AbacDao abacDao;
/** 获取abac表达式详细信息列表 */
public List<Abac> getAll() {
List<Abac> abacs = abacDao.selectList(null);
for (Abac abac : abacs) {
List<String> permissions = abacDao.selectPermissions(abac.getId());
abac.setPermissions(permissions);
}
return abacs;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserDao userDao;
/** 根据userId获取用户详细信息 */
public User get(Long userId) {
User user = userDao.selectById(userId);
List<String> repository = userDao.selectRepository(userId);
user.setContributions(repository);
return user;
}
}
4.5.security context
/**
* 自定义用户元数据 用于获取一些实体的属性、操作类型、相关的环境
*/
public interface MetadataCustomizer {
/** 自定义用户元数据 */
void customize(User user);
}
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 解析abac表达式
*/
@Component
public class SecurityContext {
/**
* SpEL表达式解析器
*/
private final ExpressionParser expressionParser = new SpelExpressionParser();
/**
* 解析abac表达式
*
* @param user 用户详细信息
* @param abacs abac表达式详细信息集合
* @return expressions集合, 将这个结果集存放到 Spring Security 或者Apache APISIX的userDetail上下文中
*/
public List<String> rbacPermissions(User user, List<Abac> abacs) {
return this.rbacPermissions(user, abacs, Collections.emptyList());
}
/**
* 解析abac表达式
*
* @param user 用户详细信息
* @param abacs abac表达式详细信息集合
* @param metadataCustomizers 自定义用户元数据 用于获取一些实体的属性、操作类型、相关的环境
* @return expressions集合, 将这个结果集存放到 Spring Security 或者Apache APISIX的userDetail上下文中
*/
public List<String> rbacPermissions(User user, List<Abac> abacs, List<MetadataCustomizer> metadataCustomizers) {
// 处理自定义元数据
metadataCustomizers.forEach(metadataCustomizer -> metadataCustomizer.customize(user));
List<String> expressions = new ArrayList<>();
for (Abac abac : abacs) {
// 解析表达式的求值器
Expression expression = expressionParser.parseExpression(abac.getExpression());
// 创建环境上下文
EvaluationContext context = new StandardEvaluationContext(user);
// 获取expression的结果
if (expression.getValue(context, boolean.class)) {
expressions.addAll(abac.getPermissions());
}
}
return expressions;
}
}
4.6.Startup class
@SpringBootApplication
public class SpringbootAbacApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAbacApplication.class, args);
}
}
4.7. Test class
@SpringBootTest
class AbacApplicationTests {
@Autowired
private UserService userService;
@Autowired
private AbacService abacService;
@Autowired
private SecurityContext securityContext;
/** 获取不同用户的abac权限 */
@Test
void testRbac() {
User user = userService.get(1L);
System.out.println(user);
List<Abac> rbac = abacService.getAll();
System.out.println(rbac);
List<String> permissions = securityContext.rbacPermissions(user, rbac);
System.out.println(permissions);
user = userService.get(2L);
System.out.println(user);
permissions = securityContext.rbacPermissions(user, rbac);
System.out.println(permissions);
user = userService.get(3L);
System.out.println(user);
permissions = securityContext.rbacPermissions(user, rbac);
System.out.println(permissions);
}
/**
* 获取自定义权限
*/
@Test
void testMetadataCustomizer() {
User user = userService.get(1L);
System.out.println(user);
List<Abac> rbac = abacService.getAll();
System.out.println(rbac);
List<String> permissions = securityContext.rbacPermissions(user, rbac);
System.out.println(permissions);
permissions = securityContext.rbacPermissions(user, rbac, getMetadataCustomizer());
System.out.println(permissions);
}
/** 模拟网络ip */
private List<MetadataCustomizer> getMetadataCustomizer() {
return new ArrayList<MetadataCustomizer>() {
{
add(user -> user.getMetadata().put("ip", "192.168.0.1"));
}};
}
}
testRbac() test effect
#用户1
User(id=1, name=魏昌进, age=26, email=[email protected], contributions=[galaxy-sea/spring-cloud-apisix], metadata={
})
#所有ABAC
[Abac(id=1, expression=contributions.contains('galaxy-sea/spring-cloud-apisix'), permissions=[github:pr:merge]), Abac(id=2, expression=name == 'admin', permissions=[github:pr:merge, github:pr:close, github:pr:open, github:pr:comment]), Abac(id=3, expression=metadata.get('ip') == '192.168.0.1', permissions=[github:pr:merge, github:pr:close, github:pr:open, github:pr:comment])]
#用户1权限
[github:pr:merge]
#用户2
User(id=2, name=test, age=1, email=[email protected], contributions=[spring-cloud/spring-cloud-commons, spring-cloud/spring-cloud-openfeign, alibaba/spring-cloud-alibaba, Tencent/spring-cloud-tencent, apache/apisix-docker], metadata={
})
#用户2权限
[]
#用户3
User(id=3, name=admin, age=1, email=[email protected], contributions=[], metadata={
})
#用户3权限
[github:pr:merge, github:pr:close, github:pr:open, github:pr:comment]
testMetadataCustomizer() test effect
#用户1
User(id=1, name=魏昌进, age=26, email=[email protected], contributions=[galaxy-sea/spring-cloud-apisix], metadata={
})
#所有ABAC
[Abac(id=1, expression=contributions.contains('galaxy-sea/spring-cloud-apisix'), permissions=[github:pr:merge]), Abac(id=2, expression=name == 'admin', permissions=[github:pr:merge, github:pr:close, github:pr:open, github:pr:comment]), Abac(id=3, expression=metadata.get('ip') == '192.168.0.1', permissions=[github:pr:merge, github:pr:close, github:pr:open, github:pr:comment])]
#解析abac表达式,获取用户1满足条件的权限
[github:pr:merge]
#自定义用户元数据 ,判断用户1是否满足ip环境权限
[github:pr:merge, github:pr:merge, github:pr:close, github:pr:open, github:pr:comment]
5.Spring Security and Apache Shiro integration
Spring Security only needs to modify the interceptor and UserDetails
convert it SecurityContext#rbacPermissions
toGrantedAuthority
/**
* 这里是伪代码, 展示一下大概逻辑
*/
public class IamOncePerRequestFilter implements OncePerRequestFilter {
@Autowired
private SecurityContext securityContext;
@Autowired
private AbacService abacService;
@Autowired
private List<MetadataCustomizer> metadataCustomizers;
@Autowired
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
UserDetails user = toUser();
List<String> permissions = securityContext.rbacPermissions(user, abacService.getAll(), metadataCustomizers);
List<GrantedAuthority> abacAuthority = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
user.getAuthorities().addAll(abacAuthority);
}
}