SpringBoot與Shiro整合-權限管理實戰實操

 

一、搭建SpringBoot開發環境

1. 安裝好開發軟件和Maven等


開發工具:Spring Tool Suite(TST) 

Maven :3.3.9

jdk:1.8

2. 創建springboot項目(此功能需要聯網)

3. pom.xml文件配置

<?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 http://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.1.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.vae</groupId>
	<artifactId>shiro</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-shiro</name>
	<description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

	<dependencies>
		<!-- thymeleaf依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <!-- Web支持:Spring web開發支持,servlet相關程序等 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
		<!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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

</project>

4. 編寫測試Controller層

package com.vae.user.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping
public class UserController {
	
	@RequestMapping("/hi")
    
	public String hi(){
		return "hi shiro";
	}
}

5. 啓動spring boot啓動類

package com.vae;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * springboot啓動類
 * @author vae
 *
 */
@SpringBootApplication
public class SpringbootShiroApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootShiroApplication.class, args);
	}
}

6. 瀏覽器訪問http://localhost:8080/hi 測試

二、使用Thymeleaf頁面模板


1. pom.xml 文件中添加 thymeleaf依賴

<!-- thymeleaf依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2. 在UserController中編寫測試thymeleaf的方法

下面寫了兩種方式,第一種比較直觀和優雅,第二種相對普遍且代碼較少,且迎合從struts2

注意:第二種方式返回String類型時,controller層不可以使用@RestController ,@RestController 等價於 @Controller 加上 @ResponseBody, @ResponseBody表示該方法的返回不會被解析爲跳轉, 而是直接寫入http響應正文。

    @RequestMapping("/index")
    public ModelAndView index() {
        ModelAndView view = new ModelAndView();
        // 設置跳轉的視圖 默認映射到 src/main/resources/templates/{viewName}.html
        view.setViewName("index");
        // 設置屬性
        view.addObject("title", "我的templates頁面");
        view.addObject("desc", "歡迎進入我的csdn博客");
        Author author = new Author();
        author.setAge(18);
        author.setEmail("[email protected]");
        author.setName("vae");
        view.addObject("author", author);
        return view;
    }

	@RequestMapping("/index1")
    public String index1(HttpServletRequest request) {
        // TODO 與上面的寫法不同,但是結果一致。
        // 設置屬性
        request.setAttribute("title", "我的templates頁面");
        request.setAttribute("desc", "歡迎進入我的csdn博客");
        Author author = new Author();
        author.setAge(18);
        author.setEmail("[email protected]");
        author.setName("vae");
        request.setAttribute("author", author);
        // 返回的 index 默認映射到 src/main/resources/templates/xxxx.html
        return "index";
    }
    
    class Author {
        private int age;
        private String name;
        private String email;
	    // 省略 get set        
    }

 

3. 在resources/templates文件夾下新建index.html模板文件

可以看到 thymeleaf 是通過在標籤中添加額外屬性動態綁定數據的

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <!-- 可以看到 thymeleaf 是通過在標籤裏添加額外屬性來綁定動態數據的 -->
    <title th:text="${title}">Title</title>
    <!-- 在/resources/static/js目錄下創建一個hello.js 用如下語法依賴即可-->
    <script type="text/javascript" th:src="@{/js/hello.js}"></script>
</head>
<body>
    <h1 th:text="${desc}">Hello World</h1>
    <h2>=====作者信息=====</h2>
        <p th:text="${author?.name}"></p>
        <p th:text="${author?.age}"></p>
        <p th:text="${author?.email}"></p>
</body>
</html>

4. 瀏覽器訪問http://localhost:8080/index測試

 

三、shiro認證-ShiroConfig配置類


1. shiro的核心API


Subject:用戶主體(關聯SecurityManager,把操作交給SecurityManager)
SecurityManager:安全管理器(關聯Realm)
Realm:shiro連接數據庫的橋樑


2. Spring整合shiro


(1)添加shiro和spring整合依賴

<!-- shiro和spring整合依賴 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
 </dependency>


(2)自定義Realm類

package com.vae.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm{

	/**
     * 授權邏輯
     */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
		System.out.println("執行授權邏輯");
		return null;
	}

	/**
     * 認證邏輯
     */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
		System.out.println("執行認證邏輯");
		return null;
	}

}


(3)編寫shiro配置類(基本結構)

package com.vae.shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * shiro的配置類
 * @author Administrator
 *
 */
@Configuration
public class ShiroConfig {
	/**
     * 創建ShiroFilterFactoryBean
     */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
	ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//      設置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		return shiroFilterFactoryBean;
	}

	/**
     * 創建DefaultWebSecurityManager
     */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getdefaultDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
		DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
		//關聯Realm
		securityManager.setRealm(userRealm);
		return securityManager;
	}

	/**
     * 創建Realm
     */

	@Bean(name="userRealm")
	public UserRealm getRealm(){
		return new UserRealm();
	}
}


四、shiro認證-使用shiro過濾器實現認證資源攔截


1. 在templates下新建user/add.html和user/update.html

è¿éåå¾çæè¿°
2. 在UserController中編寫訪問這兩個頁面的方法

	@RequestMapping("/add")
	public String add() {
		return "user/add";
	}

	@RequestMapping("/update")
	public String update() {
		return "user/update";
	}

3. 在index.html頁面中添加這兩個頁面的超鏈接

進入用戶新增頁面:<a href="add">用戶新增</a>
進入用戶更新頁面:<a href="update">用戶更新</a>

測試可訪問。

4. 修改ShiroConfig類的getShiroFilterFactoryBean方法中添加攔截

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
        //設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加shiro內置過濾器,實現權限相關的url攔截
        /**
         * 常見過濾器:
         * anon:無需認證(登錄)可以訪問
         * authc:必須認證纔可以訪問
         * user:如果使用Remember Me的功能,可以直接訪問
         * perms:該資源必須得到資源權限纔可以訪問
         * role:該資源必須得到角色權限纔可以訪問
         */
        Map<String, String> filterMap=new LinkedHashMap<String, String>();
        filterMap.put("/add", "authc");
        filterMap.put("/update", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

攔截之後重啓,再次訪問發現:試圖訪問add或者update,會自動跳轉到login.jsp頁面 

Filter 解釋
anon 無參,開放權限,可以理解爲匿名用戶或遊客
authc 無參,需要認證
logout 無參,註銷,執行後會直接跳轉到shiroFilterFactoryBean.setLoginUrl(); 設置的 url
authcBasic 無參,表示 httpBasic 認證
user 無參,表示必須存在用戶,當登入操作時不做檢查
ssl 無參,表示安全的URL請求,協議爲 https
perms[user] 參數可寫多個,表示需要某個或某些權限才能通過,多個參數時寫 perms[“user, admin”],當有多個參數時必須每個參數都通過纔算通過
roles[admin] 參數可寫多個,表示是某個或某些角色才能通過,多個參數時寫 roles[“admin,user”],當有多個參數時必須每個參數都通過纔算通過
rest[user] 根據請求的方法,相當於 perms[user:method],其中 method 爲 post,get,delete 等
port[8081] 當請求的URL端口不是8081時,跳轉到schemal://serverName:8081?queryString 其中 schmal 是協議 http 或 https 等等,serverName 是你訪問的 Host,8081 是 Port 端口,queryString 是你訪問的 URL 裏的 ? 後面的參數

       

5. 修改攔截後跳轉的頁面


(1)在templates下新增login.html頁面 
(2)在ShiroConfig中shiroFilterFactoryBean方法中修改攔截後跳轉的頁面

//修改跳轉的登錄頁面,不加此項就會跳轉到login.jsp頁面
shiroFilterFactoryBean.setLoginUrl("/toLogin");

(3)在UserController中添加toLogin方法

	@RequestMapping("/toLogin")
	public String toLogin() {
		return "login";
	}

測試,當沒有權限跳轉到該login.html頁面

6. 用通配符攔截

filterMap.put("/index", "anon");
filterMap.put("/*", "authc");

五、shiro認證-實現用戶登錄功能


1. 修改完善之前的login.html頁面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
</head>
<body>
    <h1>登錄頁面</h1>   
    <span style="color:red" th:text="${msg}"></span>
    <form method="post" action="login">
        <input type="text" name="username" /><br>
        <input type="password" name="password"><br>
        <input type="submit" name="submit" value="登錄">
    </form> 
</body>
</html>

2. 在UserController中編寫/login請求,編寫登錄的處理邏輯

 

@RequestMapping("/login")
    public String login(String username,String password,Model model) {
        /**
         * 使用shiro編寫認證操作
         */
        //獲取Subject
        Subject subject=SecurityUtils.getSubject();
        //封裝用戶數據
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        //執行登錄方法
        try {
            //只要執行login方法,就會去執行UserRealm中的認證邏輯
            subject.login(token);

            //如果沒有異常,代表登錄成功
            //跳轉到textThymeleaf頁面,代表主頁
            return "redirect:/index";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            //登錄失敗
            model.addAttribute("msg","用戶名不存在");
            return "login";

        }catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            model.addAttribute("msg","密碼錯誤");
            return "login";
        }
    }

測試發現,並沒有進入/login請求,是因爲之前寫的攔截器(/*)攔截了所有請求,再對/login請求放行,加入代碼:

filterMap.put("/login", "anon"); 


重啓測試,發現執行了認證邏輯,返回了用戶名不存在異常。

3. 在UserRealm中編寫shiro認證邏輯

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
        System.out.println("執行認證邏輯");

        //先寫模擬數據進行驗證,下一步再連接數據庫,假設數據庫的用戶名和密碼如下
        String dbusername="vae";
        String dbpassword="123456";

        //編寫shiro判斷邏輯,判斷用戶名和密碼

        //1. 判斷用戶名
        UsernamePasswordToken token=(UsernamePasswordToken) arg0;
        if (!token.getUsername().equals(dbusername)) {
            //用戶名不存在
            return null;//shiro底層會拋出UnknownAccountException
        }
        //2. 判斷密碼
        return new SimpleAuthenticationInfo("",dbpassword,"");//參數1:需要返回給login方法的數據;參數2:數據庫密碼,shiro會自動判斷 

    }



4. 啓動程序,進行測試


六、shiro認證-整合MyBatis完善用戶登錄


1. 導入Mybatis相關的依賴

        <!-- druid德魯伊連接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>

		<!-- mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!-- Spring的MyBatis啓動器 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>


2. 配置application.properties(src/main/resources下)


(1)創建數據庫及用戶表

mysql> CREATE DATABASE springboot_shiro;
Query OK, 1 row affected

mysql> use springboot_shiro;
Database changed

mysql> CREATE TABLE `user` (
  `id` int(11) NOT NULL COMMENT 'id',
  `username` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '用戶名',
  `password` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '密碼',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected


(2)配置application.properties

spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_shiro?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

# 連接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# mybatis 別名掃描
mybatis.type-aliases-package=com.fukaiit.domain

3. 編寫User實體類

package com.vae.user.entity;

public class User {
	private Integer id;
	private String username;
	private String password;

    //省略get和set方法
}

4. 編寫接口UserDao.java

package com.vae.user.dao;

import com.vae.user.entity.User;

public interface UserDao {
	/**
	 * 根據username查找用戶信息
	 * @param username
	 * @return
	 */
	public User findByName(String username);
}

5. 編寫UserMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 此處指向dao接口 -->
<mapper namespace="com.vae.shiro.user.dao.UserDao">
  <!-- 此處指向實體類 -->
  <resultMap id="BaseResultMap" type="com.vae.shiro.user.entity.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
  </resultMap>
  <sql id="Base_Column_List">
    id, username, password
  </sql>
  
  <!-- 根據username查找用戶信息 -->
  <select id="findByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
  	select 
    <include refid="Base_Column_List" />
    from user
    where username= #{username,jdbcType=VARCHAR}
  </select>  
</mapper>

6. 編寫UserService.java業務接口

package com.vae.user.service;

import com.vae.user.entity.User;

public interface UserService {
	/**
	 * 根據username查找用戶信息
	 * @param username
	 * @return
	 */
	public User findByUsername(String username);
}

7. 編寫UserServiceImpl.java業務實現類

package com.vae.user.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.vae.user.dao.UserDao;
import com.vae.user.entity.User;
import com.vae.user.service.UserService;

@Service
public class UserServiceImpl implements UserService{
	@Autowired
	private UserDao userDao;
	
	@Override
	public User findByUsername(String username) {
		return userDao.findByUsername(username);
	}

}

8. 在啓動類添加@MapperScan註解,開啓Mybatis的Mapper接口掃描

@MapperScan("com.vae.**.dao")

9. 修改UserRealm.java,調用剛編寫的業務

@Autowired
	private UserService userService;
	/**
	 * 執行認證邏輯      
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
		System.out.println("執行認證邏輯");
		// 編寫shiro判斷邏輯,判斷用戶名和密碼
		System.out.println(arg0.toString());
		// 1. 判斷用戶名
		UsernamePasswordToken token = (UsernamePasswordToken) arg0;
		User user = userService.findByUsername(token.getUsername());
		if (user==null) {
			//用戶名不存在
			return null;//shiro底層會拋出UnknownAccountException
		}
		// 2. 判斷密碼
		// 參數1:需要返回給login方法的數據;參數2:數據庫密碼,shiro會自動判斷 
		return new SimpleAuthenticationInfo("", user.getPassword(), "");
	}

10. 測試

效果相同。

七、Shiro授權-使用Shiro過濾器實現授權頁面攔截


1. 在ShiroConfig中添加過濾器

//授權過濾器:授權攔截後,shiro會自動跳轉到未授權頁面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/*", "authc");
Tips:注意要寫在/*之前,否則不會攔截

2. 添加設置未授權頁面


(1)ShiroConfig中

//修改自動跳轉的未授權頁面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");

(2)UserController中

@RequestMapping("/unAuth")
public String unAuth() {
    return "unAuth";
}

(3)添加unAuth.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>未授權頁面</title>
</head>
<body>
    <h1>抱歉!您無權限訪問!</h1>    
</body>
</html>

3. 訪問 http://localhost:8080/add 測試


登錄認證之後,訪問/add頁面會提示未授權,而/update可以正常訪問。

八、Shiro授權-編寫資源授權邏輯


剛纔打印的log日誌中可以看到,只要訪問了需要授權訪問的資源,就會執行UserRealm中的doGetAuthenticationInfo()方法,在該方法中給資源進行授權。

    /**
     * 執行授權邏輯
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        System.out.println("執行授權邏輯");
        
        //給資源進行授權
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //添加資源的授權字符串
        info.addStringPermission("user:add");
        
        return info;
    }


測試查看效果:日誌中可以看到執行了該授權邏輯,現在可以訪問/add了

九、Shiro授權-關聯數據庫動態授權


1. 修改數據表


給user表添加perms字段,插入兩個測試用戶


2. 一系列小修改


(1)User.java:添加perms屬性和getter/setter
(2)UserDao.java:

public User findById(Integer id);

(3)UserMapper.xml

  <!-- 根據id查找用戶信息 -->
  <select id="findById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
  	select 
    <include refid="Base_Column_List" />
    from user
    where id = #{id,jdbcType=INTEGER}
  </select>

(4)UserService.java

public User findById(Integer id);

(5)UserServiceImpl.java

    @Override
    public User findById(Integer id) {
        return userMapper.findById(id);
    }

(6)給/update添加資源攔截

filterMap.put("/update", "perms[user:update]");

3. 修改UserRealm中的doGetAuthorizationInfo方法

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        System.out.println("執行授權邏輯");
        
        //給資源進行授權
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        /*//添加資源的授權字符串
        info.addStringPermission("user:add");*/
        
        //獲取當前用戶
        Subject subject=SecurityUtils.getSubject();
        User user=(User)subject.getPrincipal();
        //到數據庫查詢當前登錄用戶的授權字符串
        User dbUser=userService.findById(user.getId());//通過當前登錄用戶id查找的數據庫用戶
        
        info.addStringPermission(dbUser.getPerms());        
        
        return info;
    }

將doGetAuthenticationInfo()方法的返回修改爲 

return new SimpleAuthenticationInfo(user,user.getPassword(),"");

因爲User user=(User)subject.getPrincipal(); 所取得的當前登錄用戶就是從這裏來的 

4. 登錄不同權限用戶進行測試


各自有了各自的權限。

十、ThymeLeaf和shiro標籤整合使用


1. 導入thymeleaf對shiro的擴展座標

        <!-- 導入thymeleaf對shiro的擴展座標 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2. 配置ShiroDialect


ShiroConfig中

    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

3. 在頁面上使用shiro標籤

<div shiro:hasPermission="user:add">
    進入用戶新增頁面:<a href="add">用戶新增</a>
</div>
<div shiro:hasPermission="user:update">
    進入用戶更新頁面:<a href="update">用戶更新</a>
</div>


4. 運行測試


不同權限用戶登錄,只顯示了他有權限看到的內容。

十一.項目完整目錄

借鑑地址:SpringBoot與Shiro整合-權限管理實戰視頻筆記

重新整理了一邊

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章