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.
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
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:
Role table sys_role (parent null = role, otherwise permission).
User table sys_user:
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
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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
