Spring Security 實(shí)現(xiàn)動(dòng)態(tài)權(quán)限菜單方案(附源碼)

系統(tǒng)權(quán)限管理

1进陡、前言

在實(shí)際開(kāi)發(fā)中释牺,開(kāi)發(fā)任何一套系統(tǒng)萝衩,基本都少不了權(quán)限管理這一塊。這些足以說(shuō)明權(quán)限管理的重要性没咙。其實(shí)SpringSecurity去年就學(xué)了猩谊,一直沒(méi)有時(shí)間整理,用了一年多時(shí)間了祭刚,給我的印象一直都挺好牌捷,實(shí)用,安全性高(Security可以對(duì)密碼進(jìn)行加密)涡驮。而且這一塊在實(shí)際開(kāi)發(fā)中也的確很重要宜鸯,所以這里整理了一套基于SpringSecurity的權(quán)限管理。

案例代碼下面有下載鏈接遮怜。

2淋袖、案例技術(shù)棧

如果對(duì)于SpringSecurity還不了解的話(huà)可以先了解一下SpringSecurity安全控件的學(xué)習(xí),頁(yè)面采用的是Bootstrap寫(xiě)的(頁(yè)面就簡(jiǎn)單的寫(xiě)了一下锯梁,可以根據(jù)自己的需求更改)即碗,其實(shí)后端理解了,前臺(tái)就是顯示作用陌凳,大家可以自行更換前臺(tái)頁(yè)面顯示框架剥懒,持久層使用的是Spring-Data-Jpa。

并且對(duì)后端持久層和控制器進(jìn)行了一下小封裝合敦,Java持久層和控制器的封裝初橘。頁(yè)面使用的Thymeleaf模板,SpringBoot整合Thymeleaf模板。

數(shù)據(jù)庫(kù)設(shè)計(jì)

1保檐、表關(guān)系

  • 菜單(TbMenu)=====> 頁(yè)面上需要顯示的所有菜單
  • 角色(SysRole)=====> 角色及角色對(duì)應(yīng)的菜單
  • 用戶(hù)(SysUser)=====> 用戶(hù)及用戶(hù)對(duì)應(yīng)的角色
  • 用戶(hù)和角色中間表(sys_user_role)====> 用戶(hù)和角色中間表

2耕蝉、數(shù)據(jù)庫(kù)表結(jié)構(gòu)

菜單表tb_menu

角色及菜單權(quán)限表sys_role,其中父節(jié)點(diǎn)parent 為null時(shí)為角色夜只,不為null時(shí)為對(duì)應(yīng)角色的菜單權(quán)限垒在。

用戶(hù)表sys_user

用戶(hù)和角色多對(duì)多關(guān)系,用戶(hù)和角色中間表sys_user_role(有Spring-Data-Jpa自動(dòng)生成)扔亥。

新建項(xiàng)目

1场躯、新建springboot項(xiàng)目

新建springboot項(xiàng)目,在項(xiàng)目中添加SpringSecurity相關(guān)Maven依賴(lài)旅挤,pom.map文件

<?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/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mcy</groupId>
    <artifactId>springboot-security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-security</name>
    <description>Demo project for Spring Boot</description>

    <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.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.webjars.bower</groupId>
            <artifactId>bootstrap-select</artifactId>
            <version>2.0.0-beta1</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootbox</artifactId>
            <version>4.4.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2踢关、項(xiàng)目結(jié)構(gòu)

編寫(xiě)代碼

1、編寫(xiě)實(shí)體類(lèi)

菜單表實(shí)體類(lèi)TbMenu粘茄,Spring-Data-Jpa可以根據(jù)實(shí)體類(lèi)去數(shù)據(jù)庫(kù)新建或更新對(duì)應(yīng)的表結(jié)構(gòu)签舞,詳情可以訪(fǎng)問(wèn)Spring-Data-Jpa入門(mén):

https://blog.csdn.net/qq_40205116/article/details/103039936

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;

/**
 * 菜單表
 * @author
 *
 */
@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<>();

 @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(value="idx")
 public List<TbMenu> getChildren() {
  return children;
 }

 public void setChildren(List<TbMenu> children) {
  this.children = children;
 }

 public TbMenu(Integer id) {
  super(id);
 }

 public TbMenu(){
  super();
 }

 public TbMenu(String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
  this.name = name;
  this.url = url;
  this.idx = idx;
  this.parent = parent;
  this.children = children;
 }

 public TbMenu(Integer integer, String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
  super(integer);
  this.name = name;
  this.url = url;
  this.idx = idx;
  this.parent = parent;
  this.children = children;
 }

 @Transient
 public Integer getParentId() {
  return parent==null?null:parent.getId();
 }
}

表新建好了,下面就是實(shí)現(xiàn)增刪改查就可以了驹闰,實(shí)現(xiàn)效果如下瘪菌。

新增和修改菜單。

對(duì)于Bootstrap的樹(shù)形表格嘹朗,可以移步到:BootStrap-bable-treegrid樹(shù)形表格的使用师妙。

https://blog.csdn.net/qq_40205116/article/details/103740104

菜單管理實(shí)現(xiàn)了,下一步就是實(shí)現(xiàn)角色及角色對(duì)應(yīng)的權(quán)限管理了屹培。

角色及權(quán)限表SysRole默穴,parent 為null時(shí)為角色,不為null時(shí)為權(quán)限褪秀。

package com.mcy.springbootsecurity.entity;

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
/***
 * 角色及角色對(duì)應(yīng)的菜單權(quán)限
 * @author
 *parent 為null時(shí)為角色蓄诽,不為null時(shí)為權(quán)限
 */
public class SysRole extends BaseEntity<Integer> {
 private String name; //名稱(chēng)
 private String code; //代碼
 @JsonIgnore
 private SysRole parent;
 private Integer idx; //排序
 @JsonIgnore
 private List<SysRole> children = new ArrayList<>();

 @Column(length=20)
 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getCode() {
  return code;
 }

 public void setCode(String code) {
  this.code = code;
 }

 @ManyToOne
 @CreatedBy
 public SysRole getParent() {
  return parent;
 }

 public void setParent(SysRole parent) {
  this.parent = parent;
 }

 @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
 public List<SysRole> getChildren() {
  return children;
 }

 public void setChildren(List<SysRole> children) {
  this.children = children;
 }

 //獲取父節(jié)點(diǎn)id
 @Transient
 public Integer getParentId() {
  return parent==null?null:parent.getId();
 }

 public Integer getIdx() {
  return idx;
 }

 public void setIdx(Integer idx) {
  this.idx = idx;
 }

 public SysRole(String name, String code, SysRole parent, Integer idx, List<SysRole> children) {
  this.name = name;
  this.code = code;
  this.parent = parent;
  this.idx = idx;
  this.children = children;
 }

 public SysRole(Integer id, String name, String code, SysRole parent, Integer idx, List<SysRole> children) {
  super(id);
  this.name = name;
  this.code = code;
  this.parent = parent;
  this.idx = idx;
  this.children = children;
 }

 public SysRole(Integer id) {
  super(id);
 }

 public SysRole(){}
}

首先需要實(shí)現(xiàn)角色管理,之后在角色中添加對(duì)應(yīng)的菜單權(quán)限媒吗。

實(shí)現(xiàn)效果(也可以和菜單管理一樣仑氛,用樹(shù)形表格展示,根據(jù)個(gè)人需求闸英。這里用的是樹(shù)形菜單展示的)锯岖。

給角色分配權(quán)限。

最后實(shí)現(xiàn)的就是用戶(hù)管理了甫何,只需要對(duì)添加的用戶(hù)分配對(duì)應(yīng)的角色就可以了出吹,用戶(hù)登錄時(shí),顯示角色對(duì)應(yīng)的權(quán)限辙喂。

用戶(hù)表SysUser捶牢,繼承的BaseEntity類(lèi)中就一個(gè)ID字段鸠珠。

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 用戶(hù)表
 */
@Entity
public class SysUser extends BaseEntity<Integer> {
 private String username; //賬號(hào)
 private String password; //密碼
 private String name;  //姓名
 private String address;  //地址

 @JsonIgnore
 private List<SysRole> roles=new ArrayList<>(); //角色

 @Column(length=20,unique=true)
 public String getUsername() {
  return username;
 }
 public void setUsername(String username) {
  this.username = username;
 }

 @Column(length=100)
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }

 @Column(length=20)
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }

 @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;
 }

 public String getAddress() {
  return address;
 }

 public void setAddress(String address) {
  this.address = address;
 }

 //角色名稱(chēng)
 @Transient
 public String getRoleNames() {
  String str="";
  for (SysRole role : getRoles()) {
   str+=role.getName()+",";
  }
  if(str.length()>0) {
   str=str.substring(0, str.length()-1);
  }
  return str;
 }

 //角色代碼
 @Transient
 public String getRoleCodes() {
  String str="";
  for (SysRole role : getRoles()) {
   str+=role.getCode()+",";
  }
  if(str.indexOf(",")>0) {
   str=str.substring(0,str.length()-1);
  }
  return str;
 }

}

用戶(hù)管理就是基本的數(shù)據(jù)表格,效果如圖秋麸。

2渐排、Security配置文件

Security相關(guān)配置文件,下面兩個(gè)文件如果看不懂竹勉,可以訪(fǎng)問(wèn)SpringSecurity安全控件的學(xué)習(xí)中有詳細(xì)講解飞盆。

https://blog.csdn.net/qq_40205116/article/details/103439326

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;

    /**
     * 用戶(hù)認(rèn)證操作
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //添加用戶(hù)娄琉,并給予權(quán)限
        auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("DIY");
        //設(shè)置認(rèn)證方式
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /**
     * 用戶(hù)授權(quán)操作
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();    //安全器令牌
        http.formLogin()
                //登錄請(qǐng)求被攔截
                .loginPage("/login").permitAll()
                //設(shè)置默認(rèn)登錄成功跳轉(zhuǎn)頁(yè)面
                .successForwardUrl("/main")
                .failureUrl("/login?error");   //登錄失敗的頁(yè)面
        http.authorizeRequests().antMatchers("/static/**", "/assets/**").permitAll();    //文件下的所有都能訪(fǎng)問(wèn)
        http.authorizeRequests().antMatchers("/webjars/**").permitAll();
        http.logout().logoutUrl("/logout").permitAll();     //退出
        http.authorizeRequests().anyRequest().authenticated();    //除此之外的都必須通過(guò)請(qǐng)求驗(yàn)證才能訪(fǎng)問(wèn)
    }
}

獲取登錄者相關(guān)信息次乓,工具類(lèi)。

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;

//創(chuàng)建會(huì)話(huà)孽水,獲取當(dāng)前登錄對(duì)象
@Component
public class UserUtils {
 @Autowired
 private SysUserService userService;

 /**
  * 獲取當(dāng)前登錄者的信息
  * @return 當(dāng)前者信息
  */
 public SysUser getUser() {
  //獲取當(dāng)前用戶(hù)的用戶(hù)名
  String username = SecurityContextHolder.getContext().getAuthentication().getName();
  SysUser user = userService.findByUsername(username);
  return user;
 }

 /**
  * 判斷此用戶(hù)中是否包含roleName菜單權(quán)限
  * @param roleName
  * @return
  */
 public Boolean hasRole(String roleName) {
  //獲取UserDetails類(lèi)票腰,
  UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  List<String> roleCodes=new ArrayList<>();
  for (GrantedAuthority authority : userDetails.getAuthorities()) {
   //getAuthority()返回用戶(hù)對(duì)應(yīng)的菜單權(quán)限
   roleCodes.add(authority.getAuthority());
  }
  return roleCodes.contains(roleName);
 }
}

3、動(dòng)態(tài)權(quán)限菜單加載相關(guān)方法

用戶(hù)表的SysUserService需要實(shí)現(xiàn)UserDetailsService接口女气,因?yàn)樵赟pringSecurity中配置的相關(guān)參數(shù)需要是UserDetailsService類(lèi)的數(shù)據(jù)杏慰。

重寫(xiě)UserDetailsService接口中的loadUserByUsername方法,通過(guò)該方法查詢(xún)對(duì)應(yīng)的用戶(hù)炼鞠,返回對(duì)象UserDetails是SpringSecurity的一個(gè)核心接口缘滥。其中定義了一些可以獲取用戶(hù)名,密碼谒主,權(quán)限等與認(rèn)證相關(guān)信息的方法朝扼。

重寫(xiě)的loadUserByUsername方法。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //調(diào)用持久層接口findByUsername方法查詢(xún)用戶(hù)霎肯。
    SysUser user = userRepository.findByUsername(username);
    if(user == null){
        throw new UsernameNotFoundException("用戶(hù)名不存在");
    }
    //創(chuàng)建List集合擎颖,用來(lái)保存用戶(hù)菜單權(quán)限,GrantedAuthority對(duì)象代表賦予當(dāng)前用戶(hù)的權(quán)限
    List<GrantedAuthority> authorities = new ArrayList<>();
    //獲得當(dāng)前用戶(hù)角色集合
    List<SysRole> roles = user.getRoles();
    List<SysRole> haveRoles=new ArrayList<>();
    for (SysRole role : roles) {
        haveRoles.add(role);
        List<SysRole> children = roleService.findByParent(role);
        children.removeAll(haveRoles);
        haveRoles.addAll(children);
    }
    for(SysRole role: haveRoles){
        //將關(guān)聯(lián)對(duì)象role的name屬性保存為用戶(hù)的認(rèn)證權(quán)限
        authorities.add(new SimpleGrantedAuthority(role.getName()));
    }
    //此處返回的是org.springframework.security.core.userdetails.User類(lèi)观游,該類(lèi)是SpringSecurity內(nèi)部的實(shí)現(xiàn)
    //org.springframework.security.core.userdetails.User類(lèi)實(shí)現(xiàn)了UserDetails接口
    return new User(user.getUsername(), user.getPassword(), authorities);
}

所有功能實(shí)現(xiàn)了搂捧,最后就是根據(jù)角色去顯示對(duì)應(yīng)的菜單了。

在TbMenuService類(lèi)中的findAuditMenu方法懂缕,查詢(xún)當(dāng)前用戶(hù)所擁有的權(quán)限菜單允跑。

/**
 * 獲取用戶(hù)所擁有的權(quán)限對(duì)應(yīng)的菜單項(xiàng)
 * @return
 */
public List<TbMenu> findAuditMenu() {
    List<TbMenu> menus;
    //判斷是否是后門(mén)用戶(hù)
    if(userUtils.hasRole("ROLE_DIY")){
        //查詢(xún)所有菜單,子菜單可以通過(guò)父級(jí)菜單的映射得到
        menus = menuRepository.findByParentIsNullOrderByIdx();
    }else{
        //獲取此用戶(hù)對(duì)應(yīng)的菜單權(quán)限
        menus = auditMenu(menuRepository.findByParentIsNullOrderByIdx());
    }
    return menus;
}

//根據(jù)用戶(hù)的菜單權(quán)限對(duì)菜單進(jìn)行過(guò)濾
private List<TbMenu> auditMenu(List<TbMenu> menus) {
    List<TbMenu> list = new ArrayList<>();
    for(TbMenu menu: menus){
        String name = menu.getName();
        //判斷此用戶(hù)是否有此菜單權(quán)限
        if(userUtils.hasRole(name)){
            list.add(menu);
            //遞歸判斷子菜單
            if(menu.getChildren() != null && !menu.getChildren().isEmpty()) {
                menu.setChildren(auditMenu(menu.getChildren()));
            }
        }
    }
    return list;
}

在UserUtils工具類(lèi)中的hasRole方法搪柑,判斷此用戶(hù)中是否包含roleName菜單權(quán)限聋丝。

public Boolean hasRole(String roleName) {
 //獲取UserDetails類(lèi),
 UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 List<String> roleCodes=new ArrayList<>();
 for (GrantedAuthority authority : userDetails.getAuthorities()) {
  //getAuthority()返回用戶(hù)對(duì)應(yīng)的菜單權(quán)限
  roleCodes.add(authority.getAuthority());
 }
 return roleCodes.contains(roleName);
}

之后在控制器中返回用戶(hù)對(duì)應(yīng)的菜單權(quán)限拌屏,之后在前臺(tái)頁(yè)面遍歷就可以了潮针。

@RequestMapping(value = "/main")
public String main(ModelMap map){
    //加載菜單
    List<TbMenu> menus = menuService.findAuditMenu();
    map.put("menus", menus);
    if (menus.isEmpty()) {
        return "main/main";
    }
    return "main/main1";
}

4、首頁(yè)菜單遍歷

首頁(yè)菜單遍歷倚喂,這里使用的是LayUI菜單每篷,如果其他框架可以自行根據(jù)頁(yè)面標(biāo)簽規(guī)律遍歷瓣戚,因?yàn)轫?yè)面使用的是Thymeleaf模板,不是JSP焦读,使用遍歷菜單時(shí)不是采用的EL表達(dá)式子库,而是使用的Thymeleaf自帶的標(biāo)簽表達(dá)式。

<div id="main">
    <div id="main_nav">
        <div class="panel-group" id="accordion" style="margin-bottom: 0;">
            <div th:each="menu, menuStat: ${menus}" th:if="${menu.children.size() != 0 && menu.children != null}" 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}">系統(tǒng)設(shè)置</span><span class="caret"></span>
                        </p>
                    </h4>
                </div>
                <div th:if="${menuStat.first}" th:id="|collapseOne${menuStat.index}|" class="panel-collapse collapse collapse in">
                    <div class="panel-body">
                        <p th:each="subMenu:${menu.children}" th:src="${subMenu.url}" th:text="${subMenu.name}">菜單管理</p>
                    </div>
                </div>
                <div th:if="${!menuStat.first}" th:id="|collapseOne${menuStat.index}|" class="panel-collapse collapse collapse">
                    <div class="panel-body">
                        <p th:each="subMenu:${menu.children}" th:src="${subMenu.url}" th:text="${subMenu.name}">菜單管理</p>
                    </div>
                </div>
            </div>
        </div>
        <div id="nav_p">
            <p th:each="menu:${menus}" th:if="${menu.children.size() == 0}" th:src="${menu.url}" th:text="${menu.name}">成績(jī)管理</p>
        </div>
    </div>
    <div id="main_home">
        首頁(yè)內(nèi)容
    </div>
</div>

測(cè)試應(yīng)用

1矗晃、對(duì)應(yīng)效果展示

用戶(hù)數(shù)據(jù)及對(duì)應(yīng)的角色

管理員對(duì)應(yīng)的菜單權(quán)限仑嗅。

用戶(hù)角色對(duì)應(yīng)的菜單權(quán)限。

測(cè)試用戶(hù)角色對(duì)應(yīng)的菜單權(quán)限张症。

2仓技、測(cè)試應(yīng)用

用戶(hù)名為admin1有管理員角色的用戶(hù)登錄,菜單顯示俗他。

用戶(hù)名為admin2有用戶(hù)角色的用戶(hù)登錄脖捻,菜單顯示。

用戶(hù)名為admin3有測(cè)試用戶(hù)角色的用戶(hù)登錄兆衅,菜單顯示地沮。

3、案例代碼下載

下載地址:https://github.com/machaoyin/SpringBoot-Security

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羡亩,一起剝皮案震驚了整個(gè)濱河市摩疑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌畏铆,老刑警劉巖雷袋,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異及志,居然都是意外死亡片排,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)速侈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)率寡,“玉大人,你說(shuō)我怎么就攤上這事倚搬∫惫玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵每界,是天一觀的道長(zhǎng)捅僵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)眨层,這世上最難降的妖魔是什么庙楚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮趴樱,結(jié)果婚禮上馒闷,老公的妹妹穿的比我還像新娘酪捡。我一直安慰自己,他們只是感情好纳账,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布逛薇。 她就那樣靜靜地躺著,像睡著了一般疏虫。 火紅的嫁衣襯著肌膚如雪永罚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天卧秘,我揣著相機(jī)與錄音呢袱,去河邊找鬼。 笑死斯议,一個(gè)胖子當(dāng)著我的面吹牛产捞,可吹牛的內(nèi)容都是我干的醇锚。 我是一名探鬼主播哼御,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼焊唬!你這毒婦竟也來(lái)了恋昼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赶促,失蹤者是張志新(化名)和其女友劉穎液肌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鸥滨,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗦哆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了婿滓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片老速。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凸主,靈堂內(nèi)的尸體忽然破棺而出橘券,到底是詐尸還是另有隱情,我是刑警寧澤卿吐,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布旁舰,位于F島的核電站,受9級(jí)特大地震影響嗡官,放射性物質(zhì)發(fā)生泄漏箭窜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一衍腥、第九天 我趴在偏房一處隱蔽的房頂上張望磺樱。 院中可真熱鬧芥丧,春花似錦、人聲如沸坊罢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)活孩。三九已至物遇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間憾儒,已是汗流浹背询兴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留起趾,地道東北人诗舰。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像训裆,于是被迫代替她去往敵國(guó)和親眶根。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容