1. 程式人生 > >SpringBoot整合Shiro許可權框架實戰

SpringBoot整合Shiro許可權框架實戰

什麼是ACL和RBAC

ACL

  • Access Control list:訪問控制列表
  • 優點:簡單易用,開發便捷
  • 缺點:使用者和許可權直接掛鉤,導致在授予時的複雜性,比較分散,不便於管理
  • 例子:常見的檔案系統許可權設計,直接給使用者加許可權

RBAC

  • Role Based Access Control:基於角色的訪問控制
  • 許可權與角色相關聯,使用者通過成為適當角色的成員而得到這些角色的許可權
  • 優點:簡化了使用者與許可權的管理,通過對使用者進行分類,使得角色與許可權關聯起來
  • 缺點:開發比ACL相對複雜
  • 例子:基於RBAC模型的許可權驗證框架,Apache Shiro

什麼是Apache Shiro

官網地址

點我直達

介紹

  Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程式,從最小的移動應用程式到最大的網路和企業應用程式。

什麼是身份認證

  Authentication,身份認證,一般就是登陸校驗

什麼是授權

  Authorization,給使用者分配角色或者訪問某些資源的許可權

什麼是會話管理

  Session Management,使用者的會話管理員,多數情況下是web session

什麼是加密

  Cryptography,資料加密,比如密碼加解密

核心概念

Subject

  我們把使用者或者程式稱為主體,主體去訪問系統或者資源

SecurityManager

  安全管理器,Subject的認證和授權都要在安全管理器下進行

Realm

  資料域,Shiro和安全資料的聯結器,通過realm獲取認證授權相關資訊

Authenticator

  認證器,主要負責Subject的認證

Authorizer

  授權器,主要負責Subject的授權,控制Subject擁有的角色或者許可權

Crytography

  加解密,Shiro的包含易於使用和理解的資料加解密方法,簡化了很多複雜的API

Cache Manager

  快取管理器,比如認證或授權資訊,通過快取進行管理,提高效能

快速上手

構建專案

認證和授權

package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2020/12/27 7:43 下午
 * @Versiion:1.0
 */
public class QuickStartTest {
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();

    @Before
    public void init() {
        //初始化資料來源,模擬從資料庫中取的資料
        accountRealm.addAccount("laochen", "123");
        accountRealm.addAccount("laowang", "123456");
        //構建環境
        defaultSecurityManager.setRealm(accountRealm);
    }

    @Test
    public void testAuthentication() {
        //設定上下文
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取當前主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laowang", "123456");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
    }

}
QuickStartTest.java

常用API

        //是否有對應的角色
        subject.hasRole("root");
        //獲取subject名
        subject.getPrincipal();
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷
        subject.checkRole("admin");
        //檢查是否有對應的角色
        subject.hasRole("admin");
        //退出登入
        subject.logout();

package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2020/12/27 7:43 下午
 * @Versiion:1.0
 */
public class QuickStartAPITest {
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();

    @Before
    public void init() {
        //初始化資料來源,模擬從資料庫中取的資料
        accountRealm.addAccount("laochen", "123","root","admin");
        accountRealm.addAccount("laowang", "123456","user");
        //構建環境
        defaultSecurityManager.setRealm(accountRealm);
    }

    @Test
    public void testAuthentication() {
        //設定上下文
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //獲取當前主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laochen", "123");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
        //是否有對應的角色
        System.out.println("是否有對應的root角色:"+subject.hasRole("root"));
        //獲取subject名
        System.out.println("獲取subject名:"+subject.getPrincipal());
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷,沒有的話,直接報錯
        subject.checkRole("admin");
        //檢查是否有對應的角色
        System.out.println("是否存在admin角色:"+subject.hasRole("admin"));
        //退出登入
        subject.logout();
        System.out.println("退出登入後,認證結果:" + authenticated);
    }

}
QuickStartAPITest.java
<?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.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ybchen</groupId>
    <artifactId>springboot_shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_shiro</name>
    <description>SpringBoot整合Shiro</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--Shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.0</version>
        </dependency>

    </dependencies>

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

</project>
pom.xml

realm實戰

作用

  Shiro從Realm獲取安全資料

概念

  • principal:主體的標識,可以有多個,但是需要具有唯一性,如:手機號、郵箱
  • credential:憑證,一般就是密碼

內建ini realm

package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description:從ini配置檔案中讀取使用者與角色
 * @Author:chenyanbin
 * @Date:2020/12/27 8:52 下午
 * @Versiion:1.0
 */
public class QuickStartIniTest {
    @Before
    public void init() {

    }

    @Test
    public void testAuthentication() {
        //建立SecurityManager工廠,通過配置檔案ini建立
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager=factory.getInstance();
        //將securityManager設定到當前執行環境中
        SecurityUtils.setSecurityManager(securityManager);
        //獲取主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laochen", "123");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
        //是否有對應的角色
        System.out.println("是否有對應的root角色:"+subject.hasRole("root"));
        //獲取subject名
        System.out.println("獲取subject名:"+subject.getPrincipal());
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷,沒有的話,直接報錯
        subject.checkRole("admin");
        //檢查是否有對應的角色
        System.out.println("是否存在admin角色:"+subject.hasRole("admin"));
        //退出登入
        subject.logout();
        System.out.println("退出登入後,認證結果:" + authenticated);
    }
}
QuickStartIniTest.java
# 格式 name=password,role1,role2,..roleN
[users]
# 賬戶=laochen;密碼=123;角色=admin
laochen = 123, admin
laowang = 456, user

# 格式 role=permission1,permission2...permissionN 也可以用萬用字元
# 下面配置user的許可權為所有video:find,video:buy,如果需要配置video全部操作crud 則 user = video:*
[roles]
user = video:find,video:buy
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
shiro.ini

校驗許可權

package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description:從ini配置檔案中讀取使用者與角色
 * @Author:chenyanbin
 * @Date:2020/12/27 8:52 下午
 * @Versiion:1.0
 */
public class QuickStartIniTest {
    @Before
    public void init() {

    }

    @Test
    public void testAuthentication() {
        //建立SecurityManager工廠,通過配置檔案ini建立
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager=factory.getInstance();
        //將securityManager設定到當前執行環境中
        SecurityUtils.setSecurityManager(securityManager);
        //獲取主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laochen", "123");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
        //是否有對應的角色
        System.out.println("是否有對應的root角色:"+subject.hasRole("root"));
        //獲取subject名
        System.out.println("獲取subject名:"+subject.getPrincipal());
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷,沒有的話,直接報錯
        subject.checkRole("admin");
        //檢查是否有對應的角色
        System.out.println("是否存在admin角色:"+subject.hasRole("admin"));
        //================許可權,沒有的話直接報錯================
        subject.checkPermission("video:delete");
        System.out.println("是否有video:delete許可權:"+subject.isPermitted("video:delete"));
        //退出登入
        subject.logout();
        System.out.println("退出登入後,認證結果:" + subject.isAuthenticated());
    }
}
QuickStartIniTest.java
# 格式 name=password,role1,role2,..roleN
[users]
# 賬戶=laochen;密碼=123;角色=admin
laochen = 123, admin
laowang = 456, user

# 格式 role=permission1,permission2...permissionN 也可以用萬用字元
# 下面配置user的許可權為所有video:find,video:buy,如果需要配置video全部操作crud 則 user = video:*
[roles]
user = video:find,video:buy
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
shiro.ini

注:配置檔案必須ini結尾

內建JdbcRealm

方式一

package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2020/12/27 10:40 下午
 * @Versiion:1.0
 */
public class QuickStartJdbcIniTest {
    @Test
    public void testAuthentication(){
        //建立SecurityManager工廠,通過配置檔案ini建立
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini");
        SecurityManager securityManager=factory.getInstance();
        //將securityManager設定到當前執行環境中
        SecurityUtils.setSecurityManager(securityManager);
        //獲取主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laochen", "123");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
        //是否有對應的角色
        System.out.println("是否有對應的root角色:"+subject.hasRole("root"));
        //獲取subject名
        System.out.println("獲取subject名:"+subject.getPrincipal());
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷,沒有的話,直接報錯
        subject.checkRole("role1");
        //檢查是否有對應的角色
        System.out.println("是否存在role1角色:"+subject.hasRole("role1"));
        //================許可權,沒有的話直接報錯================
        //subject.checkPermission("video:delete");
        System.out.println("是否有video:buy許可權:"+subject.isPermitted("video:buy"));
        //退出登入
        subject.logout();
        System.out.println("退出登入後,認證結果:" + subject.isAuthenticated());
    }
}
QuickStartJdbcIniTest.java
#宣告Realm,指定realm型別
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#配置資料來源
#dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource=com.alibaba.druid.pool.DruidDataSource
# mysql-connector-java 5 用的驅動url是com.mysql.jdbc.Driver,mysql-connector-java6以後用的是com.mysql.cj.jdbc.Driver
dataSource.driverClassName=com.mysql.cj.jdbc.Driver
#避免安全警告
dataSource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
#賬號、密碼
dataSource.username=root
dataSource.password=root
#指定資料來源
jdbcRealm.dataSource=$dataSource
#開啟查詢許可權, 預設是false
jdbcRealm.permissionsLookupEnabled=true
#指定SecurityManager的Realms實現,設定realms,可以有多個,用逗號隔開
securityManager.realms=$jdbcRealm
jdbcrealm.ini
/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50731
 Source Host           : localhost:3306
 Source Schema         : shiro

 Target Server Type    : MySQL
 Target Server Version : 50731
 File Encoding         : 65001

 Date: 27/12/2020 23:06:45
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for roles_permissions
-- ----------------------------
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `role_name` varchar(100) DEFAULT NULL COMMENT '角色名',
  `permission` varchar(100) DEFAULT NULL COMMENT '許可權名',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of roles_permissions
-- ----------------------------
BEGIN;
INSERT INTO `roles_permissions` VALUES (4, 'admin', 'video:*');
INSERT INTO `roles_permissions` VALUES (3, 'role1', 'video:buy');
INSERT INTO `roles_permissions` VALUES (2, 'role1', 'video:find');
INSERT INTO `roles_permissions` VALUES (5, 'role2', '*');
INSERT INTO `roles_permissions` VALUES (1, 'root', '*');
COMMIT;

-- ----------------------------
-- Table structure for user_roles
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(100) DEFAULT NULL COMMENT '使用者名稱',
  `role_name` varchar(100) DEFAULT NULL COMMENT '角色名',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_roles
-- ----------------------------
BEGIN;
INSERT INTO `user_roles` VALUES (1, 'laochen', 'role1');
INSERT INTO `user_roles` VALUES (2, 'laochen', 'role3');
INSERT INTO `user_roles` VALUES (4, 'laowang', 'admin');
INSERT INTO `user_roles` VALUES (3, 'laowang', 'root');
COMMIT;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(100) DEFAULT NULL COMMENT '使用者名稱',
  `password` varchar(100) DEFAULT NULL COMMENT '密碼',
  `password_salt` varchar(100) DEFAULT NULL COMMENT '密碼加鹽規則',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_users_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (1, 'laochen', '123', NULL);
INSERT INTO `users` VALUES (2, 'laowang', '456', NULL);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
建表語句.sql

注意

  表名和欄位要對應上,否則自定義定,繼承:AuthorizingRealm,重寫sql查詢語句!!!!並重新指定realm型別!!!!

方式二

package com.ybchen.springboot_shiro;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2020/12/27 10:40 下午
 * @Versiion:1.0
 */
public class QuickStartJdbc2Test {
    @Test
    public void testAuthentication() {
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
        ds.setUsername("root");
        ds.setPassword("root");
        JdbcRealm jdbcRealm = new JdbcRealm();
        //開啟查詢許可權, 預設是false
        jdbcRealm.setPermissionsLookupEnabled(true);
        //配置資料來源
        jdbcRealm.setDataSource(ds);
        //jdbc與DefaultSecurityManager關聯
        securityManager.setRealm(jdbcRealm);
        //=======================下面內容相同==============================
        //將securityManager設定到當前執行環境中
        SecurityUtils.setSecurityManager(securityManager);
        //獲取主體
        Subject subject = SecurityUtils.getSubject();
        //模擬使用者登入,賬戶、密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("laochen", "123");
        subject.login(usernamePasswordToken);
        //判斷是否成功
        boolean authenticated = subject.isAuthenticated();
        System.out.println("認證結果:" + authenticated);
        //是否有對應的角色
        System.out.println("是否有對應的root角色:" + subject.hasRole("root"));
        //獲取subject名
        System.out.println("獲取subject名:" + subject.getPrincipal());
        //檢查是否有對應的角色,無返回值,直接在SecurityManager裡面進行判斷,沒有的話,直接報錯
        subject.checkRole("role1");
        //檢查是否有對應的角色
        System.out.println("是否存在role1角色:" + subject.hasRole("role1"));
        //================許可權,沒有的話直接報錯================
        //subject.checkPermission("video:delete");
        System.out.println("是否有video:buy許可權:" + subject.isPermitted("video:buy"));
        //退出登入
        subject.logout();
        System.out.println("退出登入後,認證結果:" + subject.isAuthenticated());
    }
}
QuickStartJdbc2Test.java

自定義realm

  繼承AuthorizingRealm,重寫授權方法doGetAuthorizationInfo、重寫認證方法doGetAuthenticationInfo。

  UsernamePasswordToken:對應就是shiro的token中有Principal和Credential。

  SimpleAuthorizationInfo:代表使用者角色許可權資訊

  SimpleAuthenticationInfo:代表該使用者的認證資訊

package com.ybchen.springboot_shiro;

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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Description:自定義Realm
 * @Author:chenyanbin
 * @Date:2020/12/28 8:41 下午
 * @Versiion:1.0
 */
public class CustomRealm extends AuthorizingRealm {
    private final Map<String, String> userInfoMap = new HashMap<>();
    //role-->permission
    private final Map<String, Set<String>> permissionMap = new HashMap<>();
    //user-->role
    private final Map<String, Set<String>> roleMap = new HashMap<>();

    /**
     * 程式碼塊初始化資料
     */
    {
        userInfoMap.put("laochen", "123");
        userInfoMap.put("laowang", "456");
        //================================
        Set<String> set1 = new HashSet<>();
        set1.add("video:find");
        set1.add("video:buy");
        Set<String> set2 = new HashSet<>();
        set2.add("video:add");
        set2.add("video:delete");
        permissionMap.put("laochen", set1);
        permissionMap.put("laowang", set2);
        //================================
        Set<String> set3 = new HashSet<>();
        Set<String> set4 = new HashSet<>();
        set3.add("role1");
        set3.add("role2");
        set4.add("root");
        roleMap.put("laochen", set3);
        roleMap.put("laowang", set4);
    }

    /**
     * 授權認證
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授權 AuthorizationInfo");
        String name = (String) principals.getPrimaryPrincipal();
        //許可權
        Set<String> permissions = getPermissionsByNameFromDB(name);
        //角色
        Set<String> roles = getRoleByNameFromDB(name);
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    /**
     * 模擬從資料庫中取角色
     *
     * @param name
     * @return
     */
    private Set<String> getRoleByNameFromDB(String name) {
        return roleMap.get(name);
    }

    /**
     * 模擬從資料庫中取許可權
     *
     * @param name
     * @return
     */
    private Set<String> getPermissionsByNameFromDB(String name) {
        return permissionMap.get(name);
    }

    /**
     * 登入認證
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("認證 doGetAuthenticationInfo");
        //使用者名稱
        String name = (String) token.getPrincipal();
        //從DB中根據使用者取密碼
        String pwd = getPwdByUserNameFromDB(name);
        if (pwd == null || "".equals(pwd)) {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 模擬從資料庫中取密碼
     *
     * @param name
     * @return
     */
    private String getPwdByUserNameFromDB(String name) {
        return userInfoMap.get(name);
    }
}
CustomRealm.java
package com.ybchen.springboot_shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * @Description:自定義realm
 * @Author:chenyanbin
 * @Date:2020/12/28 8:43 下午
 * @Versiion:1.0
 */
public class QuickCustomRealmTest {
    private CustomRealm customRealm=new CustomRealm();
    private DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();

    @Before
    public void init() {
        //構建環境
        defaultSecurityManager.setRealm(customRealm);
        SecurityUtils.setSecurityManager(defaultSecurityManager);
    }

    @Test
    public void testAuthentication(){
        //獲取當前操作的主體
        Subject subject = SecurityUtils.getSubject();
        //使用者輸入賬號、密碼
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("laochen","123");
        subject.login(usernamePasswordToken);
        System.out.println("認證結果:"+subject.isAuthenticated());
        //拿到主體標識屬性
        System.out.println("獲取subject名:"+subject.getPrincipal());
        //是否有role1角色,沒有則報錯
        subject.checkRole("role1");
        //是否有對應的角色
        System.out.println("是否有對應的角色:"+subject.hasRole("role1"));
        //是否有對應的許可權
        System.out.println("是否有對應的許可權:"+subject.isPermitted("video:find"));
    }
}
QuickCustomRealmTest.java

Filter過濾器

  • 核心過濾器
    • DefaultFilter,配置那個路徑對應那個攔截器進行處理
  • authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    • 需要認證登入才能訪問
  • user:org.apache.shiro.web.filter.authc.UseerrFilter
    • 使用者攔截器,表示必須存在使用者
  • anon:org.apache.shiro.web.filter.authc.AnonymoousFilter
    • 匿名攔截器,不需要登入即可訪問的資源,匿名使用者或遊客,一般用於過濾靜態資源。
  • roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    • 角色授權攔截器,驗證使用者是否擁有角色
    • 引數可寫多個,表示某些角色才能通過,多個引數時,寫roles["root,role1"],當有多個引數時必須每個引數都通過才算通過
  • perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
    • 許可權授權攔截器,驗證使用者是否擁有許可權
    • 引數可寫多個,表示需要某些許可權才能通過,多個引數寫perms["user,admin"],當有多個引數時必須每個引數都通過才算可以
  • authcBasci:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    • httpBasic,身份驗證攔截器
  • logout:org.apache.shiro.web.filter.authc.LogoutFilter
    • 退出攔截器,執行後會直接跳轉到shiroFilterFactoryBean.setLoginUrl(),設定的url
  • port:org.apache.shiro.web.filter.authz.PortFilter
    • 埠攔截器,可通過的埠
  • ssl:org.apache.shiro.web.filter.authz.SslFilter
    • ssl攔截器,只有請求協議是https才能通過

Filter配置路徑

  • 路徑萬用字元支援?、*、**,注意萬用字元匹配不包含目錄分隔符“/”
  • *:可以匹配所有,不加*,可以進行字首匹配,但多個冒號就需要多個*來匹配
url許可權採取第一次匹配優先的方式
?:匹配一個字元,如:/user?,匹配:/user1,但不匹配:/user/
*:匹配零個或多個字串,如:/add*,匹配:/addtest,但不匹配:/user/1
**:匹配路徑中的零個或多個路徑,如:/user/**將匹配:/user/xxx/yyy

Shiro許可權控制註解

註解方式

  • @RequiresRoles(value={"admin","editor"},logical=Logical.AND)
    • 需要角色:admin和editor兩個角色,AND表示兩個同時成立
  • RequiresPermissions(value={"user:add","user:del"},logical.OR)
    • 需要許可權user:add或user:del許可權其中一個,OR是或的意思
  • @RequiresAuthentication
    • 已經授過權,呼叫Subject.isAuthenticated()返回true
  • @RequiresUser
    • 身份驗證或通過記住我登入過的

使用檔案的方式

  使用ShiroConfig。

程式設計方式

SpringBoot整合Shiro

技術棧

  前後端分離+SpringBoot+Mysql+Mybatis+Shiro+Redis+JDK8

資料庫表

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50731
 Source Host           : localhost:3306
 Source Schema         : shiro_2

 Target Server Type    : MySQL
 Target Server Version : 50731
 File Encoding         : 65001

 Date: 03/01/2021 22:36:28
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(255) DEFAULT NULL COMMENT '許可權名稱',
  `url` varchar(255) DEFAULT NULL COMMENT '路徑',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='許可權';

-- ----------------------------
-- Records of permission
-- ----------------------------
BEGIN;
INSERT INTO `permission` VALUES (1, 'video_update', '/api/video/update');
INSERT INTO `permission` VALUES (2, 'video_delete', '/api/video/delete');
INSERT INTO `permission` VALUES (3, 'video_add', '/api/video/add');
INSERT INTO `permission` VALUES (4, 'order_list', '/api/order/list');
INSERT INTO `permission` VALUES (5, 'user_list', '/api/user/list');
COMMIT;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(255) DEFAULT NULL COMMENT '角色名稱',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='角色';

-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'admin', '系統管理員');
INSERT INTO `role` VALUES (2, 'root', '超級管理員');
INSERT INTO `role` VALUES (3, 'user', '普通使用者');
COMMIT;

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `role_id` int(11) DEFAULT NULL COMMENT '角色id',
  `permission_id` int(11) DEFAULT NULL COMMENT '許可權id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='角色-許可權';

-- ----------------------------
-- Records of role_permission
-- ----------------------------
BEGIN;
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
INSERT INTO `role_permission` VALUES (3, 2, 1);
INSERT INTO `role_permission` VALUES (4, 2, 2);
INSERT INTO `role_permission` VALUES (5, 2, 3);
INSERT INTO `role_permission` VALUES (6, 2, 4);
INSERT INTO `role_permission` VALUES (7, 2, 5);
INSERT INTO `role_permission` VALUES (8, 3, 5);
COMMIT;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(255) DEFAULT NULL COMMENT '使用者名稱',
  `password` varchar(255) DEFAULT NULL COMMENT '密碼',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `salt` varchar(255) DEFAULT NULL COMMENT '加鹽',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='使用者';

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'laochen', '123', NULL, NULL);
INSERT INTO `user` VALUES (2, 'laowang', '456', NULL, NULL);
INSERT INTO `user` VALUES (3, 'laoli', '789', NULL, NULL);
COMMIT;

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `role_id` int(11) DEFAULT NULL COMMENT '角色id',
  `user_id` int(11) DEFAULT NULL COMMENT '使用者id',
  `remark` varchar(255) DEFAULT NULL COMMENT '備註',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='角色-使用者關聯表';

-- ----------------------------
-- Records of user_role
-- ----------------------------
BEGIN;
INSERT INTO `user_role` VALUES (1, 1, 1, 'laochen是系統管理員');
INSERT INTO `user_role` VALUES (2, 2, 2, 'laowang是超級管理員');
INSERT INTO `user_role` VALUES (3, 3, 3, 'laoli是普通使用者');
INSERT INTO `user_role` VALUES (4, 1, 2, 'laowang是系統管理員');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
shiro_2.sql

專案結構

package com.ybchen.springboot_shiro.config;

import com.ybchen.springboot_shiro.domain.Role;
import com.ybchen.springboot_shiro.domain.User;
import com.ybchen.springboot_shiro.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Description:自定義realm
 * @Author:chenyanbin
 * @Date:2021/1/2 11:16 下午
 * @Versiion:1.0
 */
public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    /**
     * 進行許可權校驗的時候會呼叫
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("CustomRealm doGetAuthorizationInfo 授權");
        //獲取使用者名稱
        String userName = (String) principals.getPrimaryPrincipal();
        User user = userService.findAllUserInfoByUserName(userName);
        if (user == null) {
            return null;
        }
        //角色集合
        List<String> stringRoleList = new ArrayList<>();
        //許可權集合
        List<String> stringPermissionList = new ArrayList<>();
        List<Role> roleList = user.getRoleList();
        stringRoleList = roleList.stream().map(
                obj -> {
                    stringPermissionList.addAll(obj.getPermissionList()
                            .stream()
                            .map(per ->
                                    per.getName()).collect(Collectors.toList()));
                    return obj.getName();
                }).collect(Collectors.toList());
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(stringRoleList);
        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
        return simpleAuthorizationInfo;
    }

    /**
     * 使用者登入的時候會呼叫
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("CustomRealm doGetAuthenticationInfo 認證");
        //從token中獲取使用者資訊
        String uesrName = (String) token.getPrincipal();
        User user = userService.findAllUserInfoByUserName(uesrName);
        if (user == null) {
            return null;
        }
        //密碼
        String pwd = user.getPassword();
        if (pwd == null || "".equals(pwd)) {
            return null;
        }
        return new SimpleAuthenticationInfo(uesrName, pwd, this.getClass().getName());
    }
}
CustomRealm.java
package com.ybchen.springboot_shiro.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * @Description:自定義SessionManager
 * @Author:chenyanbin
 * @Date:2021/1/3 4:54 下午
 * @Versiion:1.0
 */
public class CustomSessionManager extends DefaultWebSessionManager {
    public static final String AUTHORIZATION="token";

    public CustomSessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //獲取sessionId
        String sessionId= WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (sessionId!=null){
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        }else {
            return super.getSessionId(request,response);
        }
    }
}
CustomSessionManager.java
package com.ybchen.springboot_shiro.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 4:12 下午
 * @Versiion:1.0
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfig ShiroFilterFactoryBean 執行");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //設定SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //如果訪問需要登入的某個介面,卻沒有登入,則呼叫此介面(如果不是前後端分離,則跳轉頁面)
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
        //shiroFilterFactoryBean.setLoginUrl("/xxx.jsp");
        //登入成功後,跳轉的連結,若前後端分離,沒必要設定這個
        //shiroFilterFactoryBean.setSuccessUrl("");
        //登入成功,未授權會呼叫此方法
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
        //攔截路徑,必須使用:LinkedHashMap,要不然攔截效果會時有時無,因為使用的是無序的Map
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //key=正則表示式路徑,value=org.apache.shiro.web.filter.mgt.DefaultFilter
        //退出過濾器
        filterChainDefinitionMap.put("/logout", "logout");
        //匿名可以訪問,遊客模式
        filterChainDefinitionMap.put("/pub/**", "anon");
        //登入使用者才可以訪問
        filterChainDefinitionMap.put("/authc/**", "authc");
        //管理員角色才能訪問
        filterChainDefinitionMap.put("/admin/**", "roles[admin]");
        //有編輯許可權才能訪問
        filterChainDefinitionMap.put("/video/update", "perms[video_update]");
        //authc:url必須通過認證才可以訪問
        //anon:url可以匿名訪問
        //過濾鏈是順序執行,從上而下,一般把/**,放到最下面
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //如果不是前後端分離,不用設定setSessionManager
        securityManager.setSessionManager(sessionManager());
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 自定義realm
     *
     * @return
     */
    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        //因為資料庫密碼存的是明文,所以無需使用雙重md5校驗
//        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }

    /**
     * 密碼驗證器,雙重md5
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //設定雜湊演算法,使用md5演算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //雜湊次數,使用2次md5演算法,相當於md5(md5(xxx))
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定義SessionManager
     *
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
        CustomSessionManager customSessionManager = new CustomSessionManager();
        //超時時間,預設 30分鐘,會話超時,單位毫秒
//        customSessionManager.setGlobalSessionTimeout(200000);
        return customSessionManager;
    }
}
ShiroConfig.java
package com.ybchen.springboot_shiro.controller;

import com.ybchen.springboot_shiro.utils.JsonData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 7:22 下午
 * @Versiion:1.0
 */
@RestController
@RequestMapping("admin")
public class AdminController {
    @GetMapping("/video/video_list")
    public JsonData videoList() {
        List<String> list = Arrays.asList("docker", "k8s", "jenkins");
        return JsonData.buildSuccess(list);
    }
}
AdminController.java
package com.ybchen.springboot_shiro.controller;

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

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 10:01 下午
 * @Versiion:1.0
 */
@RestController
public class LogoutController {
//    /**
//     * 退出,沒必要能這個,退出時,前端直接將token清空即可
//     * 還需要獲取前端傳來的token,然後從shiro從清空指定的session_id
//     * @return
//     */
//    @GetMapping("logout")
//    public JsonData logout(){
//        Subject subject= SecurityUtils.getSubject();
//        subject.logout();
//        return JsonData.buildSuccess("退出成功");
//    }
}
LogoutController.java
package com.ybchen.springboot_shiro.controller;

import com.ybchen.springboot_shiro.utils.JsonData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 6:28 下午
 * @Versiion:1.0
 */
@RestController
@RequestMapping("authc")
public class OrderController {
    /**
     * 購買記錄
     * @return
     */
    @GetMapping("/video/play_record")
    public JsonData findMyPlayRecord(){
        Map<String,String> recordMap=new HashMap<>();
        recordMap.put("1","SpringBoot");
        recordMap.put("2","SpringMvc");
        return JsonData.buildSuccess(recordMap);
    }
}
OrderController.java
package com.ybchen.springboot_shiro.controller;

import com.ybchen.springboot_shiro.utils.JsonData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 9:20 下午
 * @Versiion:1.0
 */
@RestController
public class OtherController {
    @GetMapping("a")
    public JsonData a(){
        return JsonData.buildSuccess("ok");
    }
}
OtherController.java
package com.ybchen.springboot_shiro.controller;

import com.ybchen.springboot_shiro.domain.UserQuery;
import com.ybchen.springboot_shiro.utils.JsonData;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 1:12 上午
 * @Versiion:1.0
 */
@RestController
@RequestMapping("pub")
public class PublicController {
    /**
     * 需要登入
     *
     * @return
     */
    @GetMapping("need_login")
    public JsonData needLogin() {
        return JsonData.buildSuccess(-1, "溫馨提示:請使用對應的賬號登入");
    }

    /**
     * 沒許可權
     *
     * @return
     */
    @GetMapping("not_permit")
    public JsonData notPermit() {
        return JsonData.buildSuccess(-1, "溫馨提示:拒絕訪問,沒許可權");
    }

    /**
     * 首頁
     *
     * @return
     */
    @GetMapping("index")
    public JsonData index() {
        List<String> list = Arrays.asList("SpringBoot", "SpringMvc", "Mysql", "Redis");
        return JsonData.buildSuccess(list);
    }

    /**
     * 登入介面
     *
     * @param userQuery
     * @param request
     * @param response
     * @return
     */
    @PostMapping("login")
    public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response) {
        //拿到主體
        Subject subject = SecurityUtils.getSubject();
        try {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userQuery.getUserName(), userQuery.getPassword());
            subject.login(usernamePasswordToken);
            Map<String,Object> info=new HashMap<>();
            info.put("msg","登入成功");
            info.put("session_id",subject.getSession().getId());
            return JsonData.buildSuccess(info);
        }catch (Exception e){
            e.printStackTrace();
            return JsonData.buildError("賬號或密碼錯誤");
        }
    }
}
PublicController.java
package com.ybchen.springboot_shiro.controller;

import com.ybchen.springboot_shiro.utils.JsonData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 9:41 下午
 * @Versiion:1.0
 */
@RestController
@RequestMapping("video")
public class VideoController {
    @GetMapping("update")
    public JsonData updateVideo() {
        return JsonData.buildSuccess("更新成功");
    }
    @GetMapping("add")
    public JsonData add(){
        return JsonData.buildSuccess("新增成功");
    }
}
VideoController.java
package com.ybchen.springboot_shiro.dao;

import com.ybchen.springboot_shiro.domain.Permission;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * 許可權
 */
public interface PermissionMapper {
    /**
     * 根據roleId查詢所有許可權
     * @param roleId
     * @return
     */
    @Select("select p.id id,p.name name,p.url url from role_permission rp " +
            "left join permission p on rp.permission_id=p.id " +
            "where rp.role_id=#{roleId}")
    List<Permission> findByPermissionListByRoleId(@Param("roleId") int roleId);
}
PermissionMapper.java
package com.ybchen.springboot_shiro.dao;

import com.ybchen.springboot_shiro.domain.Role;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
 * 角色
 */
public interface RoleMapper {
    /**
     * 根據使用者查詢所有的角色
     *
     * @param userId 使用者id
     * @return
     */
    @Select("select r.id id,r.name name,r.description description  from  user_role ur " +
            "left join role r on ur.role_id=r.id " +
            "where ur.user_id=#{userId}")
    @Results(
            value = {
                    @Result(id = true, property = "id", column = "id"),
                    @Result(property = "name", column = "name"),
                    @Result(property = "description", column = "description"),
                    @Result(property = "permissionList", column = "id",
                            many = @Many(select = "com.ybchen.springboot_shiro.dao.PermissionMapper.findByPermissionListByRoleId",
                                    fetchType = FetchType.DEFAULT))
            }
    )
    List<Role> findRoleListByUserId(@Param("userId") int userId);
}
RoleMapper.java
package com.ybchen.springboot_shiro.dao;

import com.ybchen.springboot_shiro.domain.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * 使用者
 */
public interface UserMapper {
    /**
     * 根據使用者名稱查詢使用者
     *
     * @param userName 使用者名稱
     * @return
     */
    @Select("select * from user where username=#{userName}")
    User findByUserName(@Param("userName") String userName);

    /**
     * 根據主鍵查詢使用者
     *
     * @param id 主鍵
     * @return
     */
    @Select("select * from user where id=#{userId}")
    User findById(@Param("userId") int id);

    /**
     * 根據使用者名稱和密碼查詢使用者
     *
     * @param userName 使用者名稱
     * @param password 密碼
     * @return
     */
    @Select("select * from user where userName=#{userName} and password=#{password}")
    User findByUserNameAndPassword(@Param("userName") String userName, @Param("password") String password);
}
UserMapper.java
package com.ybchen.springboot_shiro.domain;

/**
 * @Description:許可權
 * @Author:chenyanbin
 * @Date:2021/1/2 11:47 下午
 * @Versiion:1.0
 */
public class Permission {
    /**
     * 主鍵
     */
    private int id;
    /**
     * 許可權名稱
     */
    private String name;
    /**
     * 路徑
     */
    private String url;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

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

    @Override
    public String toString() {
        return "Permission{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", url='" + url + '\'' +
                '}';
    }
}
Permission.java
package com.ybchen.springboot_shiro.domain;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description:角色
 * @Author:chenyanbin
 * @Date:2021/1/2 11:43 下午
 * @Versiion:1.0
 */
public class Role {
    /**
     * 主鍵
     */
    private int id;
    /**
     * 角色名稱
     */
    private String name;
    /**
     * 描述
     */
    private String description;
    /**
     * 許可權集合
     */
    private List<Permission> permissionList=new ArrayList<>();

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Permission> getPermissionList() {
        return permissionList;
    }

    public void setPermissionList(List<Permission> permissionList) {
        this.permissionList = permissionList;
    }

}
Role.java
package com.ybchen.springboot_shiro.domain;

/**
 * @Description:角色許可權
 * @Author:chenyanbin
 * @Date:2021/1/2 11:44 下午
 * @Versiion:1.0
 */
public class RolePermission {
    /**
     * 主鍵
     */
    private int id;
    /**
     * 角色id
     */
    private int roleId;
    /**
     * 許可權id
     */
    private int permissiionId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }

    public int getPermissiionId() {
        return permissiionId;
    }

    public void setPermissiionId(int permissiionId) {
        this.permissiionId = permissiionId;
    }

    @Override
    public String toString() {
        return "RolePermission{" +
                "id=" + id +
                ", roleId=" + roleId +
                ", permissiionId=" + permissiionId +
                '}';
    }
}
RolePermission.java
package com.ybchen.springboot_shiro.domain;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Description:使用者表
 * @Author:chenyanbin
 * @Date:2021/1/2 11:41 下午
 * @Versiion:1.0
 */
public class User {
    /**
     * 主鍵
     */
    private int id;
    /**
     * 使用者名稱
     */
    private String username;
    /**
     * 密碼
     */
    private String password;
    /**
     * 建立時間
     */
    private Date createTime;
    /**
     * 密碼加鹽
     */
    private String salt;
    /**
     * 角色集合
     */
    private List<Role> roleList=new ArrayList<>();

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;