Backend Development 19 min read

Spring Boot Security Permission Management Tutorial with Role and Menu Implementation

This article provides a step‑by‑step guide to building a Spring Boot permission management system using Spring Security, Spring Data JPA, Thymeleaf and Bootstrap, covering database design, entity classes, Maven setup, security configuration, dynamic menu loading, front‑end rendering and testing with full source code examples.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Spring Boot Security Permission Management Tutorial with Role and Menu Implementation

In this article the author, a Java architect, presents a complete Spring Boot security permission management solution based on Spring Security, Spring Data JPA and Thymeleaf.

The technology stack includes Spring Boot 2.2.2, Spring Security, Spring Data JPA, Thymeleaf, Bootstrap and MySQL.

Database design consists of four tables: TbMenu (menu), SysRole (role and permission), sys_user_role (user‑role mapping) and SysUser (user). Entity classes for each table are shown, with JPA annotations and helper methods.

Project initialization is demonstrated with a Maven pom.xml that declares the necessary dependencies and the Spring Boot Maven plugin:

<?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.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>
        <!-- other dependencies omitted for brevity -->
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Entity code for the menu table TbMenu is provided in full:

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
{
    private String name;
    private String url;
    private Integer idx;
    @JsonIgnore
    private TbMenu parent;
    @JsonIgnore
    private List
children = new ArrayList<>();

    @Column(unique = true)
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }
    public Integer getIdx() { return idx; }
    public void setIdx(Integer idx) { this.idx = idx; }
    @ManyToOne
    @CreatedBy
    public TbMenu getParent() { return parent; }
    public void setParent(TbMenu parent) { this.parent = parent; }
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
    @OrderBy("idx")
    public List
getChildren() { return children; }
    public void setChildren(List
children) { this.children = children; }
    @Transient
    public Integer getParentId() { return parent == null ? null : parent.getId(); }
    // constructors omitted for brevity
}

Similar full entity definitions are given for SysRole and SysUser , each with JPA annotations, relationship mappings and transient helper methods.

The security configuration class WebSecurityConfig sets up authentication, password encoding, login page, logout handling and request authorizations:

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/**", "/webjars/**").permitAll();
        http.logout().logoutUrl("/logout").permitAll();
        http.authorizeRequests().anyRequest().authenticated();
    }
}

A utility component UserUtils retrieves the current logged‑in SysUser and provides a hasRole(String roleName) method to check role membership:

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
roleCodes = new ArrayList<>();
        for (GrantedAuthority authority : userDetails.getAuthorities()) {
            roleCodes.add(authority.getAuthority());
        }
        return roleCodes.contains(roleName);
    }
}

Dynamic menu loading is performed in TbMenuService by filtering menus according to the user's roles:

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

private List
auditMenu(List
menus) {
    List
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;
}

The controller returns the filtered menu list to a Thymeleaf view where a LayUI accordion renders the hierarchical structure:

@RequestMapping(value = "/main")
public String main(ModelMap map) {
    List
menus = menuService.findAuditMenu();
    map.put("menus", menus);
    return menus.isEmpty() ? "main/main" : "main/main1";
}

Screenshots in the original article demonstrate role‑based menu visibility for users admin1 , admin2 and admin3 . A GitHub repository (https://github.com/machaoyin/SpringBoot-Security) is provided for downloading the complete source code.

BackendJavaSpring BootPermission ManagementRole-Based Access ControlSpring Security
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login 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.