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:對應就是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={"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;
    }

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

    public String getMsg() {
        return msg;
    }

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

    @Override
    public String toString() {
        return "JsonData{" +
                "code=" + code +
                ", data=" + 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 = "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=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(分布式緩存)還不了解的小伙伴,在這里我就不一一講解了,可以看我以前寫過的博客。

添加依賴

        <!--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=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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM