SoFunction
Updated on 2025-04-14

Example of SpringBoot implementing RBAC permission verification model

Recently I want to build a management system to record my own consumption records and implement functions such as uploading files. I happened to see the RBAC model recently and wanted to learn it. I will share the implementation process here to facilitate viewing in the future.

The RBAC model simply means creating 5 tables (user table, role table, permission table, role and user table, role and permission table)

By assigning different roles to users and assigning different permissions to roles, users' permission control can be achieved

However, the project I did this time is a backend management system, which is mainly used by myself and friends, so the role is actually divided into administrators and ordinary users. Therefore, I omitted the role and role permission tables. Although this is not flexible enough, there are few people to use it, so it is very convenient to manage.

Database table

The database mainly creates three tables

User table:

Mainly used to log in using users

  • id User primary key
  • account Log in to account
  • password login password
  • type distinguishes between ordinary roles and administrator identities
  • username Username
  • status user status
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int         | NO   | PRI | NULL    | auto_increment |
| account   | char(15)    | YES  |     | NULL    |                |
| password  | varchar(50) | YES  |     | NULL    |                |
| type      | int         | YES  |     | NULL    |                |
| username  | varchar(20) | YES  |     | NULL    |                |
| status    | int         | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+

Permission table:

Mainly used to record permission information

  • id permission primary key
  • name permission name
  • parent_id parent permission id null if there is no parent permission
  • status permissions are enabled
  • remark permission notes
  • level permission level, mainly used for sorting fields
+-------------+-------------+------+-----+---------+----------------+
| Field       | Type        | Null | Key | Default | Extra          |
+-------------+-------------+------+-----+---------+----------------+
| id          | int         | NO   | PRI | NULL    | auto_increment |
| name        | varchar(20) | YES  |     | NULL    |                |
| parent_id   | int         | YES  |     | NULL    |                |
| status      | int         | YES  |     | NULL    |                |
| remark      | varchar(50) | YES  |     | NULL    |                |
| create_user | int         | YES  |     | NULL    |                |
| create_date | date        | YES  |     | NULL    |                |
| update_user | int         | YES  |     | NULL    |                |
| update_date | date        | YES  |     | NULL    |                |
| level       | int         | YES  |     | NULL    |                |
+-------------+-------------+------+-----+---------+----------------+

User permission table:

A table that implements many-to-many relationships

  • id primary key
  • user_id user ID
  • permission_id permission ID
+---------------+------+------+-----+---------+----------------+
| Field         | Type | Null | Key | Default | Extra          |
+---------------+------+------+-----+---------+----------------+
| id            | int  | NO   | PRI | NULL    | auto_increment |
| user_id       | int  | YES  | MUL | NULL    |                |
| permission_id | int  | YES  |     | NULL    |                |
+---------------+------+------+-----+---------+----------------+

The permission table is mainly explained. Because the management system needs to determine whether to provide a menu based on the permissions, I define the menu as a first-level permission (parent_id=null), and the addition, deletion, modification and search under the first-level permissions as a second-level permission (parent_id=corresponding first-level permissions)

If the user has certain permissions, create a record in the user permissions table

Backend implementation

The backend is very convenient to implement. The method I use is to define an annotation to scan the annotation through AOP, and then determine whether the current user has the corresponding permissions. If no exception is thrown directly, it will be captured through the global exception processor.

Define annotation class:

Because the implementation of background management is very simple, there are almost no nesting of multiple tables between each other, I used a value to represent the ID of the permissions that the current controller needs to use.

@Retention()
@Target()
public @interface PermissionCheckAnnotation {
    int value();
}

Define the enumeration class:

Mainly used to define the ID value of the corresponding permissions

public class PermissionEnum {
    public final static int USER_QUERY = 14;
    public final static int USER_UPDATE = 15;
    public final static int USER_CREATE = 16;
}

Define AOP facets

    @Pointcut("execution(Scan all files in the current package) && @annotation()")
    public void pt(){}

    @Around("pt()")
    public Object PermissionCheck(ProceedingJoinPoint jp) throws Throwable {
        MethodSignature signature = (MethodSignature) ();
// Reflection gets permission ID        PermissionCheckAnnotation annotation = ().getAnnotation();
        int value = ();
        // Get user ID        Integer userId = ();
        // Check whether there is an association between the current user and the permission ID. If there is no exception thrown        Integer hasPermission = (value, userId);
        if (hasPermission == null || hasPermission == 0){
            throw new PermissionException("Insufficient permissions, please contact the administrator");
        }
        return ();
    }

Define controller

    @PermissionCheckAnnotation(PermissionEnum.USER_CREATE)
    @PostMapping("/createUser")
    public Result<String> createUser() {
    }

Backend modification permissions

The front-end sends the modified permissions over, and the general format is an integer array ([1,2,3,4,5]), an ID set representing the current user's full permissions.

The main idea of ​​permission update is to delete all permissions of the user and then rebuild it.

If you think it's too slow, I think you can take operations such as intersection to improve efficiency, or define an additional field to determine the status of the current user permissions (delete field). This should be more efficient than delete insert

But I am a rookie and a lazy dog. How simple it is, how come it is

Delete permissions that users do not own

    <delete >
        delete from user_permission where user_id = #{userId}
        and permission_id not in
        <foreach item="item" collection="permissionList" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>

Insert new permissions

Since it is all inserted, permissions may be duplicated, so I add unique constraints to user_id and permission_id and ignore the error when inserting

    <insert >
        insert ignore into user_permission
        <foreach collection="permissionList" open="values" item="item" separator=",">
            (null,#{userId},#{item})
        </foreach>
    </insert>

front end

My front-end is not very good, and I can only draw an interface, so I have not implemented button-level permission control, but the general idea is to customize the instructions, and then check whether I have the corresponding permissions. If there is no permission, I will hide the button directly

As for menu-level permissions, my approach is to send a request every time to determine how many menus can be used. Even if someone digs out other interfaces, the backend also checks the permissions, but the data still cannot be obtained.

Here we mainly record how to modify permissions on the front end

The data returned by the query is roughly as follows

{
    id: 15
    level: 2
    name: "Operation Query"
    parentId: 1
    remark: "Query function under the operation menu"
    status: 1
}
{
    id: 1
    level: 1
    name: "Operation Menu"
    parentId: null
    remark: "Operation Menu"
    status: 1
}

I want to implement the display and operation function in a tree control in element-Plus, and roughly need to turn the above data into this format

const data: Tree[] = [
  {
    label: 'Level one 1',
    children: [
      {
        label: 'Level two 1-1',
      },
    ],
  },
]

This is implemented through dfs (there will not be intersection between permissions at the same level, so it should be done with the minimum spanning tree, but I am a lazy dog, so I wrote the easiest way)

const st = new Map();

for (let i = 0; i < ; i++) {
    (res[i].id, 0);
}
const list = [];
for (let i = 0; i < ; i++) {
    if (res[i].parentId == null) {
        const children = dfs(i, res);
        (children);
     }
}
 = list;

function dfs(idx, res) {
    (res[idx].id, 1);
    const children = []
    for (let i = 0; i < ; i++) {
        if ((res[i].id) == 1) {
            continue;
        }
        if (res[i].parentId == res[idx].id) {
            (dfs(i, res))
        }
    }
    if ( == 0)
        return { id: res[idx].id, label: res[idx].name }
    return { id: res[idx].id, label: res[idx].name, children }
}

Then throw the operation data into elTree

<el-tree v-loading="treeLoading"
@check="checkChange" 
style="max-width: 200px" 
ref="elTreeRef"
:data="tree"
:props="defaultProps" 
show-checkbox 
node-key="id" />

Then there is the initial permissions of the user. By clicking on different users, check the permissions that the user has from the backend. el-tree has a property that can set the check box that is checked by default, but I don’t know why when clicking on different users, the check box that was checked in the tree will not be cancelled. Therefore, the el-tree method is used. When clicking on different users, the passed data is an integer array ([1,2,3]), indicating the id of the selected node. You need to specify node-key in the el-tree tag in advance.

()

Finally, the permission modification is. It is roughly the user clicks on different check boxes, selects or cancels different permissions, binds the @check event, and then obtains the data inside. Here I will say that when clicking the child check box, the parent check box will also be triggered once. When all the child check boxes under the parent check box are cancelled, the parent check box will also be cancelled. Through the following method, you can get the currently checked check box and then send the data.

    const checkChange = function (data1, data2) {
        currentPermission = [..., ...]
    }

Summarize

In this way, a simple permission verification model was implemented. Although the role table is missing, the general functions are quite different. After adding the role table, you may also have to consider the migration of permissions, the allocation of permissions, etc.

But it's still very hard for a simple system

Also, why did csdn set the size of the cover image, what did my Kazmi look like?

This is the end of this article about SpringBoot's example implementation of RBAC permission verification model. For more related SpringBoot RBAC permission verification, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!