主頁 > 後端開發 > SpringBoot整合Shiro權限框架實戰

SpringBoot整合Shiro權限框架實戰

2021-01-06 06:30:18 後端開發

什么是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:對應就是shirotoken中有PrincipalCredential

  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=https://www.cnblogs.com/chenyanbin/p/{"admin","editor"},logical=Logical.AND)
    • 需要角色:admin和editor兩個角色,AND表示兩個同時成立
  • RequiresPermissions(value=https://www.cnblogs.com/chenyanbin/p/{"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=https://www.cnblogs.com/chenyanbin/p/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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", createTime=" + createTime +
                ", salt='" + salt + '\'' +
                ", roleList=" + roleList +
                '}';
    }
}
User.java
package com.ybchen.springboot_shiro.domain;

import java.io.Serializable;

/**
 * @Description:接收用戶名和密碼
 * @Author:chenyanbin
 * @Date:2021/1/3 6:19 下午
 * @Versiion:1.0
 */
public class UserQuery implements Serializable {
    private String userName;
    private String password;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "UserQuery{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
UserQuery.java
package com.ybchen.springboot_shiro.domain;

/**
 * @Description:用戶角色
 * @Author:chenyanbin
 * @Date:2021/1/2 11:46 下午
 * @Versiion:1.0
 */
public class UserRole {
    /**
     * 主鍵
     */
    private int id;
    /**
     * 角色id
     */
    private int roleId;
    /**
     * 用戶id
     */
    private int userId;
    /**
     * 備注
     */
    private String remark;

    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 getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "UserRole{" +
                "id=" + id +
                ", roleId=" + roleId +
                ", userId=" + userId +
                ", remark='" + remark + '\'' +
                '}';
    }
}
UserRole.java
package com.ybchen.springboot_shiro.exception;

/**
 * @Description:自定義例外
 * @Author:chenyanbin
 * @Date:2021/1/3 7:31 下午
 * @Versiion:1.0
 */
public class CustomException extends RuntimeException{
    private Integer code;
    private String msg;

    public CustomException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "CustomException{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                '}';
    }
}
CustomException.java
package com.ybchen.springboot_shiro.exception;

import com.ybchen.springboot_shiro.utils.JsonData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @ClassName:GlobalExceptiions
 * @Description:TODO
 * @Author:chenyb
 * @Date:2020/12/9 11:34 上午
 * @Versiion:1.0
 */
@ControllerAdvice
public class GlobalExceptiions {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public JsonData handle(Exception ex) {
        logger.info("[ 全域例外 ] ===============》 {}", ex);
        if (ex instanceof CustomException) {
            CustomException customException = (CustomException) ex;
            return JsonData.buildError(customException.getCode(), customException.getMsg());
        }
        return JsonData.buildError("系統內部錯誤,請聯系管理員!");
    }
}
GlobalExceptiions.java
package com.ybchen.springboot_shiro.service.impl;

import com.ybchen.springboot_shiro.dao.RoleMapper;
import com.ybchen.springboot_shiro.dao.UserMapper;
import com.ybchen.springboot_shiro.domain.Role;
import com.ybchen.springboot_shiro.domain.User;
import com.ybchen.springboot_shiro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 1:15 上午
 * @Versiion:1.0
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public User findAllUserInfoByUserName(String userName) {
        User user = userMapper.findByUserName(userName);
        //用戶角色的集合
        List<Role> roleList = roleMapper.findRoleListByUserId(user.getId());
        user.setRoleList(roleList);
        return user;
    }

    @Override
    public User findSimpleUserInfoById(int userId) {
        return userMapper.findById(userId);
    }

    @Override
    public User findSimpleUserInfoByUserName(String userName) {
        return userMapper.findByUserName(userName);
    }
}
UserServiceImpl.java
package com.ybchen.springboot_shiro.service;

import com.ybchen.springboot_shiro.domain.User;

public interface UserService {
    /**
     * 獲取全部用戶資訊,包括角色、權限
     * @param userName
     * @return
     */
    User findAllUserInfoByUserName(String userName);

    /**
     * 獲取用戶基本資訊
     * @param userId
     * @return
     */
    User findSimpleUserInfoById(int userId);

    /**
     * 根據用戶名查詢用戶資訊
     * @param userName
     * @return
     */
    User findSimpleUserInfoByUserName(String userName);
}
UserService.java
package com.ybchen.springboot_shiro.utils;

import java.io.Serializable;

public class JsonData implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 狀態碼,0表示成功過,1表示處理中,-1表示失敗
     */
    private Integer code;
    /**
     * 業務資料
     */
    private Object data;
    /**
     * 資訊描述
     */
    private String msg;

    public JsonData() {
    }

    public JsonData(Integer code, Object data, String msg) {
        this.code = code;
        this.data =https://www.cnblogs.com/chenyanbin/p/ data;
        this.msg = msg;
    }

    /**
     * 成功,不用回傳資料
     *
     * @return
     */
    public static JsonData buildSuccess() {
        return new JsonData(0, null, null);
    }

    /**
     * 成功,回傳資料
     *
     * @param data 回傳資料
     * @return
     */
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, null);
    }

    /**
     * 成功,回傳資料
     *
     * @param code 狀態碼
     * @param data 回傳資料
     * @return
     */
    public static JsonData buildSuccess(int code, Object data) {
        return new JsonData(code, data, null);
    }

    /**
     * 失敗,回傳資訊
     *
     * @param msg 回傳資訊
     * @return
     */
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }

    /**
     * 失敗,回傳資訊和狀態碼
     *
     * @param code 狀態碼
     * @param msg  回傳資訊
     * @return
     */
    public static JsonData buildError(Integer code, String msg) {
        return new JsonData(code, null, msg);
    }

    public Integer getCode() {
        return code;
    }

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

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data =https://www.cnblogs.com/chenyanbin/p/ data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "JsonData{" +
                "code=" + code +
                ", data="https://www.cnblogs.com/chenyanbin/p/+ data +", msg='" + msg + '\'' +
                '}';
    }
}
JsonData.java
package com.ybchen.springboot_shiro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//掃描mapper
@MapperScan(value = "https://www.cnblogs.com/chenyanbin/p/com.ybchen.springboot_shiro.dao")
public class SpringbootShiroApplication {

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

}
SpringbootShiroApplication.java
server.port=12888
#============資料庫=================
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro_2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
# 使用阿里巴巴druid資料源,默認使用自帶的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 開啟控制臺列印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# mybatis下劃線轉駝峰配置
mybatis.configuration.map-underscore-to-camel-case=true
application.properties
package com.ybchen.springboot_shiro;

import org.apache.shiro.crypto.hash.SimpleHash;
import org.junit.Test;

/**
 * @Description:
 * @Author:chenyanbin
 * @Date:2021/1/3 10:12 下午
 * @Versiion:1.0
 */
public class Md5Test {
    @Test
    public void testMd5(){
        String hashName="md5";
        String pwd="123";
        SimpleHash simpleHash = new SimpleHash(hashName, pwd, null, 2);
        System.out.println(simpleHash);
    }
}
Md5Test.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>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>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.0</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

    </dependencies>

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

</project>
pom.xml

專案原始碼

鏈接: https://pan.baidu.com/s/1adjwICKge83YcPycE8ZaEQ  密碼: if9s

專案postman測驗

127.0.0.1:12888/pub/index

127.0.0.1:12888/pub/not_permit

127.0.0.1:12888/pub/need_login

127.0.0.1:12888/pub/login

127.0.0.1:12888/authc/video/play_record

127.0.0.1:12888/admin/video/video_list

127.0.0.1:12888/video/add

127.0.0.1:12888/video/update

備注

  因為鏈接較多,就不一一做gif動圖了,直接匯入專案原始碼,請求的時候,在header上加入token即可~

Filter過濾器

業務需求

  • 一個介面,可以讓2個角色中的任意一個訪問
  • 自定義一個類,繼承:AuthorizationFilter
package com.ybchen.springboot_shiro.config;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;

/**
 * @Description:自定義Filter
 * @Author:chenyanbin
 * @Date:2021/1/4 11:14 下午
 * @Versiion:1.0
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        //filterChainDefinitionMap.put("/admin/**", "roles[admin,user]"); mappedValue <==> admin,user
        String[] rolesArray = (String[]) mappedValue;
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        Set<String> roles = CollectionUtils.asSet(rolesArray);
        //當前subject是roles中的任意一個,則有權限訪問
        for (String role : roles) {
            if (subject.hasRole(role)) {
                return true;
            }
        }
        return false;
    }
}

    @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");

        //設定自定義Filter
        Map<String, Filter> filterMap=new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        //攔截路徑,必須使用:LinkedHashMap,要不然攔截效果會時有時無,因為使用的是無序的Map
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //key=正則運算式路徑,value=https://www.cnblogs.com/chenyanbin/p/org.apache.shiro.web.filter.mgt.DefaultFilter
        //退出過濾器
        filterChainDefinitionMap.put("/logout", "logout");
        //匿名可以訪問,游客模式
        filterChainDefinitionMap.put("/pub/**", "anon");
        //登錄用戶才可以訪問
        filterChainDefinitionMap.put("/authc/**", "authc");
        //管理員角色才能訪問
//        filterChainDefinitionMap.put("/admin/**", "roles[admin,user]");
        filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin,user]");
        //有編輯權限才能訪問
        filterChainDefinitionMap.put("/video/update", "perms[video_update]");
        //authc:url必須通過認證才可以訪問
        //anon:url可以匿名訪問
        //過濾鏈是順序執行,從上而下,一般把/**,放到最下面
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

Redis整合CacheManager

原因

  授權的時候每次都去查詢資料庫,對于頻繁訪問的介面,性能和回應速度比較慢,此處可以使用快取,提高回應速度,也可以使用Guava(本地記憶體快取),

  Redis(分布式快取)還不了解的小伙伴,在這里我就不一一講解了,可以看我以前寫過的博客,

  • Redis 從入門到精通:點我直達
  • Redis 微信搶紅包,電商場景下秒殺系統設計:點我直達
  • Redis 高級專案實戰:點我直達

添加依賴

        <!--shiro+redis-->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.3.1</version>
        </dependency>

在ShiroConfig中添加如下代碼

//使用自定義cacheManager
    securityManager.setCacheManager(cacheManager());

    /**
     * 配置redisManager
     * @return
     */
    public RedisManager getRedisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost("127.0.0.1:6379");
        //連接那個資料庫
        redisManager.setDatabase(0);
        //設定密碼
//        redisManager.setPassword("123");
        return redisManager;
    }

    /**
     * 設定具體cache實作類
     * @return
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
        return redisCacheManager;
    }

修改CustomRealm

設定redis快取過期時間

Redis整合SessionManager

為啥Session也要持久化

  重啟應用,用戶無感知,可以繼續以原先的狀態繼續訪問,

修改shiroconfig

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.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
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");

        //設定自定義Filter
        Map<String, Filter> filterMap=new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        //攔截路徑,必須使用:LinkedHashMap,要不然攔截效果會時有時無,因為使用的是無序的Map
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //key=正則運算式路徑,value=https://www.cnblogs.com/chenyanbin/p/org.apache.shiro.web.filter.mgt.DefaultFilter
        //退出過濾器
        filterChainDefinitionMap.put("/logout", "logout");
        //匿名可以訪問,游客模式
        filterChainDefinitionMap.put("/pub/**", "anon");
        //登錄用戶才可以訪問
        filterChainDefinitionMap.put("/authc/**", "authc");
        //管理員角色才能訪問
//        filterChainDefinitionMap.put("/admin/**", "roles[admin,user]");
        filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin,user]");
        //有編輯權限才能訪問
        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());
        //使用自定義cacheManager
        securityManager.setCacheManager(cacheManager());
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 配置redisManager
     * @return
     */
    public RedisManager getRedisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost("127.0.0.1:6379");
        //連接那個資料庫
        redisManager.setDatabase(0);
        //設定密碼
//        redisManager.setPassword("123");
        return redisManager;
    }

    /**
     * 設定具體cache實作類
     * @return
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
        //設定快取過期時間
        redisCacheManager.setExpire(20);
        return redisCacheManager;
    }

    /**
     * 自定義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);
        //配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());
        return customSessionManager;
    }

    /**
     * 自定義session持久化
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO=new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());
        return redisSessionDAO;
    }
}
ShiroConfig.java

Shiro整合Redis后的原始碼

鏈接: https://pan.baidu.com/s/1cNQfBiw50A-U5izzOQclpw  密碼: 6wqt

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/245040.html

標籤:Java

上一篇:「美團」Java崗150道面試題:集合+JVM+設計模式+spring+Redis等

下一篇:Java高并發16-LongAdder類原始碼決議(下)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more