springboot implements ACL+RBAC permission system

Based on the premise that permission control of the web system is very important, this article introduces how to implement a complete permission system in the springboot project from the two aspects of ALC and RBAC permission control.

Source code download: https://gitee.com/skyblue0678/springboot-demo

Prologue

A backend management system basically has its own permission system. There are two types of permission systems: control list-based permissions and role-based permissions.

Permissions based on control lists are also called ACL authentication systems. For each permission, you can understand that each controller method has its own user list, and only users who exist in this list can access this method.

Role-based permissions mean that each user has his or her own role, and roles have access permissions to multiple controller methods. Users and roles, and roles and interfaces are all in a many-to-many relationship.

ACL: Access Control List

Introduction: A permission design that was very popular in the past, its core mainly lies in the direct link between users and permissions.

Advantages: simple to use and convenient to develop.

Disadvantages: Users are directly linked to permissions, which leads to complexity in granting permissions. It is relatively scattered and not easy to manage.

Example: Common file systems directly give users home permissions. For example, give the user read and write permissions.

RBAC: Role Based Access Control

Introduction: Role-based access control system. Permissions are associated with roles, and users gain permissions from these roles by becoming members of the appropriate roles.

Advantages: Simplifies the management of users and permissions, and classifies users to associate them with roles and permissions.

Disadvantages: It is more complicated to develop than ACL.

Example: Permission verification framework and application Apache Shiro and Spring security based on REAC model.

Chapter 1 ACL Permission Control

Table structure design

As mentioned above, ACL is directly linked to permissions and users. We need to design database tables to store user and permission information. The following is a simplified table structure design:

sql:

DROP TABLE IF EXISTS `acl`;
CREATE TABLE `acl` (
  `id` int NOT NULL,
  `user_id` int DEFAULT NULL,
  `permission_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` int NOT NULL AUTO_INCREMENT,
  `permission` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Initialization data:

--Insert user data
INSERT INTO User (id, username, password) VALUES (1, 'jack', '1');
INSERT INTO User (id, username, password) VALUES (2, 'rose', '1');
  
-- Insert permission data
INSERT INTO Permission (id, permission) VALUES (1, 'device:list');
INSERT INTO Permission (id, permission) VALUES (2, 'device:add');

  
--Insert ACL data
INSERT INTO ACL (id, user_id, permission_id) VALUES (1, 1, 1);
INSERT INTO ACL (id, user_id, permission_id) VALUES (2, 2, 2);

ACL permission design code

After having the three tables of user, permission and acl, the corresponding add, delete, modify and query functions can be implemented according to matisplus.

Mapper

public interface UserMapper extends BaseMapper<User> {
}

public interface PermissionMapper extends BaseMapper<Permission> {
}

public interface AclMapper extends BaseMapper<Acl> {
}

Service

public interface UserService extends IService<User> {
    User getByUsername(String username);
}


public interface PermissionService extends IService<Permission> {
    List<Permission> findByIds(Set<Integer> permIds);
}

public interface AclService extends IService<Acl> {

    List<Permission> findByUserId(Integer id);
}

impl

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public User getByUsername(String username) {
        return baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName,username));
    }
}


@Service
@Slf4j
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {
    @Override
    public List<Permission> findByIds(Set<Integer> permIds) {
        return baseMapper.selectBatchIds(permIds);
    }
}

@Service
@Slf4j
public class AclServiceImpl extends ServiceImpl<AclMapper, Acl> implements AclService {

    @Resource
    PermissionService permissionService;

    @Override
    public List<Permission> findByUserId(Integer id) {
        List<Acl> acls = baseMapper.selectList(new LambdaQueryWrapper<Acl>().eq(Acl::getUserId, id));
        Set<Integer> permIds = acls.stream().map(Acl::getPermissionId).collect(Collectors.toSet());
        return permissionService.findByIds(permIds);
    }
}

Finally, obtain the user’s permissions in login.

@GetMapping("/acl/login")
public String loginAcl(String username,String password){
    User user = userService.getByUsername(username);
    if(Objects.nonNull(user)){
        if(!user.getPwd().equals(password)){
            return "Wrong password!";
        }
        //Get the ACL permission list based on the user
        List<Permission> permissionList = aclService.findByUserId(user.getId());
        return permissionList.toString();
    }else {
        return "Username does not exist";
    }
}

test:

Browser access: http://localhost:8080/acl/login?username=jack & amp;password=1

[Permission(id=1, permission=device:list)]

In this example, we obtain the corresponding permission list through user login. In subsequent chapters, we will do actual permission verification.

Chapter 2 RBAC permission control

RBAC has an additional role concept than ACL. Users have roles, and roles correspond to specific permissions, so control is more flexible.

Table structure design

The following is a simplified table structure design:

sql

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role_name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `role` VALUES ('1', 'Device Administrator');

DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role_id` int DEFAULT NULL,
  `permission_id` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `role_permission` VALUES ('1', '1', '1');

DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int DEFAULT NULL,
  `role_id` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `user_role` VALUES ('1', '1', '1');

RBAC login method

@GetMapping("/rbac/login")
public String loginRbac(String username,String password){
    User user = userService.getByUsername(username);
    if(Objects.nonNull(user)){
        if(!user.getPwd().equals(password)){
            return "Wrong password!";
        }
        //Get the ACL permission list based on the user
        List<Permission> permissionList = userService.findByUserRole(user.getId());
        return permissionList.toString();
    }else {
        return "Username does not exist";
    }
}

Query permissions based on user role

@Override
public List<Permission> findByUserRole(Integer id) {
    return baseMapper.findByUserRole(id);
}

mapper

public interface UserMapper extends BaseMapper<User> {
    List<Permission> findByUserRole(@Param("id") Integer id);
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.demo.mapper.UserMapper">


    <select id="findByUserRole" resultType="com.it.demo.entity.Permission">
        SELECT
            e.*
        FROM
            USER a
                LEFT JOIN user_role b ON a.id = b.user_id
                LEFT JOIN role c ON b.role_id = c.id
                LEFT JOIN role_permission d ON c.id = d.role_id
                LEFT JOIN permission e ON d.permission_id = e.id
        WHERE
            a.id = #{id}
    </select>
</mapper>

Visit: http://localhost:8080/rbac/login?username=jack & amp;password=1

[Permission(id=1, permission=device:list)]

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 134074 people are learning the system