Master Spring Security: Build a Robust Role‑Based Permission System in SpringBoot

This guide walks through creating a complete SpringBoot permission management system using SpringSecurity, covering database design, Maven setup, entity classes, security configuration, utility methods, dynamic menu loading, Thymeleaf menu rendering, and testing with role‑based access control.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Master Spring Security: Build a Robust Role‑Based Permission System in SpringBoot

System Permission Management

1. Introduction

Permission management is essential for any system. This article consolidates a SpringSecurity‑based solution, including source code download links.

2. Technology Stack

The project uses SpringBoot, SpringSecurity, Spring‑Data‑JPA, Thymeleaf, and Bootstrap for the front‑end.

Database Design

1. Table Relationships

Table relationships
Table relationships

Menu (TbMenu) – all menus displayed on the UI.

Role (SysRole) – roles and their associated menus.

User (SysUser) – users and their assigned roles.

User‑Role join table (sys_user_role) – many‑to‑many mapping.

2. Table Structures

Menu table tb_menu:

Menu table
Menu table

Role table sys_role (parent null = role, otherwise permission).

Role table
Role table

User table sys_user:

User table
User table

Project Setup

1. Create SpringBoot Project

Add SpringSecurity dependencies in pom.xml:

<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.2.RELEASE</version>
    <relativePath/>
  </parent>
  <groupId>com.mcy</groupId>
  <artifactId>springboot-security</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </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.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

2. Project Structure

Project structure
Project structure

Code Implementation

1. Entity Classes

Menu entity TbMenu (Spring‑Data‑JPA will auto‑create the table):

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class TbMenu extends BaseEntity<Integer> {
    private String name;
    private String url;
    private Integer idx;
    @JsonIgnore
    private TbMenu parent;
    @JsonIgnore
    private List<TbMenu> children = new ArrayList<>();
    // getters, setters, constructors omitted for brevity
    @Transient
    public Integer getParentId() {
        return parent == null ? null : parent.getId();
    }
}

Role entity SysRole (parent null = role, otherwise permission):

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class SysRole extends BaseEntity<Integer> {
    private String name;
    private String code;
    @JsonIgnore
    private SysRole parent;
    private Integer idx;
    @JsonIgnore
    private List<SysRole> children = new ArrayList<>();
    // getters, setters, constructors omitted for brevity
    @Transient
    public Integer getParentId() {
        return parent == null ? null : parent.getId();
    }
}

User entity SysUser (many‑to‑many with roles):

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class SysUser extends BaseEntity<Integer> {
    private String username;
    private String password;
    private String name;
    private String address;
    @JsonIgnore
    private List<SysRole> roles = new ArrayList<>();
    // getters, setters, constructors omitted for brevity
    @ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    @JoinTable(name = "sys_user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
    public List<SysRole> getRoles() { return roles; }
    public void setRoles(List<SysRole> roles) { this.roles = roles; }
    @Transient
    public String getRoleNames() {
        StringBuilder sb = new StringBuilder();
        for (SysRole role : getRoles()) sb.append(role.getName()).append(",");
        if (sb.length() > 0) sb.setLength(sb.length() - 1);
        return sb.toString();
    }
    @Transient
    public String getRoleCodes() {
        StringBuilder sb = new StringBuilder();
        for (SysRole role : getRoles()) sb.append(role.getCode()).append(",");
        if (sb.length() > 0) sb.setLength(sb.length() - 1);
        return sb.toString();
    }
}

2. Security Configuration

package com.mcy.springbootsecurity.security;
import com.mcy.springbootsecurity.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SysUserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("DIY");
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.formLogin()
            .loginPage("/login").permitAll()
            .successForwardUrl("/main")
            .failureUrl("/login?error");
        http.authorizeRequests().antMatchers("/static/**", "/assets/**").permitAll();
        http.authorizeRequests().antMatchers("/webjars/**").permitAll();
        http.logout().logoutUrl("/logout").permitAll();
        http.authorizeRequests().anyRequest().authenticated();
    }
}

3. Utility Class

package com.mcy.springbootsecurity.util;
import com.mcy.springbootsecurity.entity.SysUser;
import com.mcy.springbootsecurity.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

@Component
public class UserUtils {
    @Autowired
    private SysUserService userService;

    public SysUser getUser() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        return userService.findByUsername(username);
    }

    public Boolean hasRole(String roleName) {
        UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<String> roleCodes = new ArrayList<>();
        for (GrantedAuthority authority : userDetails.getAuthorities()) {
            roleCodes.add(authority.getAuthority());
        }
        return roleCodes.contains(roleName);
    }
}

4. Dynamic Permission Menu Loading

In SysUserService (implements UserDetailsService) the loadUserByUsername method builds a list of GrantedAuthority from the user's roles and their child permissions.

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    SysUser user = userRepository.findByUsername(username);
    if (user == null) throw new UsernameNotFoundException("用户名不存在");
    List<GrantedAuthority> authorities = new ArrayList<>();
    List<SysRole> haveRoles = new ArrayList<>();
    for (SysRole role : user.getRoles()) {
        haveRoles.add(role);
        List<SysRole> children = roleService.findByParent(role);
        children.removeAll(haveRoles);
        haveRoles.addAll(children);
    }
    for (SysRole role : haveRoles) {
        authorities.add(new SimpleGrantedAuthority(role.getName()));
    }
    return new User(user.getUsername(), user.getPassword(), authorities);
}

The TbMenuService.findAuditMenu method filters menus based on the current user's roles:

public List<TbMenu> findAuditMenu() {
    List<TbMenu> menus;
    if (userUtils.hasRole("ROLE_DIY")) {
        menus = menuRepository.findByParentIsNullOrderByIdx();
    } else {
        menus = auditMenu(menuRepository.findByParentIsNullOrderByIdx());
    }
    return menus;
}

private List<TbMenu> auditMenu(List<TbMenu> menus) {
    List<TbMenu> list = new ArrayList<>();
    for (TbMenu menu : menus) {
        if (userUtils.hasRole(menu.getName())) {
            list.add(menu);
            if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
                menu.setChildren(auditMenu(menu.getChildren()));
            }
        }
    }
    return list;
}

5. Front‑End Menu Rendering (Thymeleaf)

Menus are displayed using a collapsible panel layout. Example snippet:

<div th:each="menu, menuStat : ${menus}" th:if="${menu.children.size() != 0}" class="panel panel-default">
    <div class="panel-heading">
        <h4 class="panel-title">
            <p data-toggle="collapse" data-parent="#accordion" th:href="|#collapseOne${menuStat.index}|">
                <span th:text="${menu.name}">System Settings</span>
                <span class="caret"></span>
            </p>
        </h4>
    </div>
    <div th:id="|collapseOne${menuStat.index}|" class="panel-collapse collapse">
        <div class="panel-body">
            <p th:each="subMenu : ${menu.children}" th:src="${subMenu.url}" th:text="${subMenu.name}">Menu Item</p>
        </div>
    </div>
</div>

6. Testing the Application

Three test users demonstrate role‑based menu visibility:

admin1 – administrator role (full menu).

admin2 – regular user role (limited menu).

admin3 – test user role (custom menu).

Screenshots (omitted) show the menus rendered according to each role.

Download the full source code: https://github.com/machaoyin/SpringBoot-Security
Thymeleafrole-based accessjpaspring-securitypermission-management
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.