国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

從零開(kāi)發(fā)一個(gè)自己的Shiro+Vue通用后臺(tái)管理系統(tǒng)(附源碼)

這篇具有很好參考價(jià)值的文章主要介紹了從零開(kāi)發(fā)一個(gè)自己的Shiro+Vue通用后臺(tái)管理系統(tǒng)(附源碼)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

包含前后端技術(shù)棧、功能介紹、角色權(quán)限設(shè)置及數(shù)據(jù)庫(kù)設(shè)計(jì)等內(nèi)容,附源碼下載鏈接,是學(xué)習(xí)權(quán)限管理系統(tǒng)的優(yōu)質(zhì)資源。

前言

最近一直在學(xué)習(xí)權(quán)限框架,光學(xué)不敲,那肯定不行,所以有了這個(gè)項(xiàng)目。項(xiàng)目實(shí)現(xiàn)了jwt無(wú)狀態(tài)登錄、redis緩存、token續(xù)期和可控。算是個(gè)比較通用且有亮點(diǎn)的權(quán)限管理項(xiàng)目吧。??

一、項(xiàng)目介紹

1.運(yùn)行

Shiro+Vue通用后臺(tái)管理系統(tǒng)源碼,Vue權(quán)限管理,Vue后臺(tái)管理 

項(xiàng)目下載

gitee:https://gitee.com/wusupweilgy/springboot-vue.git

藍(lán)奏云:https://wwp.lanzoup.com/iR1AY0ttqcob

2.技術(shù)棧

前端:vue2,element-ui、axios、echars組件

后端:springboot、mybatis-plus、shiro、jwt、redis

3.功能

  • 用戶、角色和菜單的權(quán)限管理

  • 統(tǒng)計(jì)在線人數(shù)、注冊(cè)人數(shù)等

  • 個(gè)人密碼、用戶信息的修改

  • 根據(jù)角色不同,前端動(dòng)態(tài)渲染菜單導(dǎo)航

4.角色權(quán)限介紹

  • admin角色擁有刪除功能,其他角色沒(méi)有

  • admin和vip角色能查看用戶信息,普通用戶不行

  • admin和vip能進(jìn)行添加操作,普通用戶不行

  • 個(gè)人信息和修改密碼,登錄過(guò)就可以訪問(wèn)

二、流程講解

1.用戶點(diǎn)擊注冊(cè),系統(tǒng)將密碼加密后存入數(shù)據(jù)庫(kù)中。

Shiro+Vue通用后臺(tái)管理系統(tǒng)源碼,Vue權(quán)限管理,Vue后臺(tái)管理

2.用戶登錄,主要是校驗(yàn)賬號(hào)密碼并生成 token(jwt),然后存儲(chǔ)到Redis,這里存的是簽發(fā)時(shí)間,比token(jwt)中設(shè)置的過(guò)期時(shí)間長(zhǎng),為了實(shí)現(xiàn)token的自動(dòng)續(xù)期。文章末尾我有細(xì)說(shuō)。 

Shiro+Vue通用后臺(tái)管理系統(tǒng)源碼,Vue權(quán)限管理,Vue后臺(tái)管理

3.用戶訪問(wèn)需要認(rèn)證的資源時(shí),需要進(jìn)行token校驗(yàn)和續(xù)期判斷

Shiro+Vue通用后臺(tái)管理系統(tǒng)源碼,Vue權(quán)限管理,Vue后臺(tái)管理

三、數(shù)據(jù)庫(kù)

E-R圖設(shè)計(jì)

沒(méi)有加外鍵,因?yàn)樵黾訒?huì)造成數(shù)據(jù)庫(kù)壓力。實(shí)體表都加入了邏輯刪除字段。

Shiro+Vue通用后臺(tái)管理系統(tǒng)源碼,Vue權(quán)限管理,Vue后臺(tái)管理

數(shù)據(jù)庫(kù)腳本

/*
 Navicat Premium Data Transfer

 Source Server         : local
 Source Server Type    : MySQL
 Source Server Version : 80028
 Source Host           : localhost:3306
 Source Schema         : shiro_jwt_vue2

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

 Date: 22/04/2023 14:18:39
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for files
-- ----------------------------
DROP TABLE IF EXISTS `files`;
CREATE TABLE `files`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名稱',
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件類型',
  `size` bigint(0) NULL DEFAULT NULL COMMENT '文件大小(kb)',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '下載鏈接',
  `md5` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件md5',
  `is_delete` tinyint(1) NULL DEFAULT 0 COMMENT '是否刪除',
  `enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否禁用鏈接',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 73 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of files
-- ----------------------------
INSERT INTO `files` VALUES (73, 'lgy.jpg', 'jpg', 35, 'http://localhost:9090/files/2023/04/22/20230422123301000000166.jpg', 'eb81db8974a4924ba39ccc049c078516', 0, 1, '2023-04-22 00:33:01', NULL);
INSERT INTO `files` VALUES (74, 'lgy.png', 'png', 197, 'http://localhost:9090/files/2023/04/22/20230422123303000000698.png', '466ebb0a2ea027b04ab2f60f2dcbf1f6', 0, 1, '2023-04-22 00:33:04', NULL);

-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict`  (
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名稱',
  `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '內(nèi)容',
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '類型'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES ('user', 'el-icon-user', 'icon');
INSERT INTO `sys_dict` VALUES ('house', 'el-icon-house', 'icon');
INSERT INTO `sys_dict` VALUES ('menu', 'el-icon-menu', 'icon');
INSERT INTO `sys_dict` VALUES ('s-custom', 'el-icon-s-custom', 'icon');
INSERT INTO `sys_dict` VALUES ('s-grid', 'el-icon-s-grid', 'icon');
INSERT INTO `sys_dict` VALUES ('document', 'el-icon-document', 'icon');
INSERT INTO `sys_dict` VALUES ('coffee', 'el-icon-coffee\r\n', 'icon');
INSERT INTO `sys_dict` VALUES ('s-marketing', 'el-icon-s-marketing', 'icon');
INSERT INTO `sys_dict` VALUES ('files', 'el-icon-files', 'icon');

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名稱',
  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '路徑',
  `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '圖標(biāo)',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
  `permission` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '權(quán)限標(biāo)識(shí)',
  `pid` int(0) NULL DEFAULT NULL COMMENT '父級(jí)id',
  `page_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '頁(yè)面路徑',
  `sort_num` int(0) NULL DEFAULT NULL COMMENT '排序',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  `is_delete` tinyint(0) NULL DEFAULT 0 COMMENT '是否刪除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 48 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (4, '系統(tǒng)管理', NULL, 'el-icon-s-grid', NULL, NULL, NULL, NULL, 300, NULL, NULL, 0);
INSERT INTO `sys_menu` VALUES (5, '用戶管理', '/user', 'el-icon-user', NULL, 'user', 4, 'User', 301, NULL, '2023-04-20 10:17:23', 0);
INSERT INTO `sys_menu` VALUES (6, '角色管理', '/role', 'el-icon-s-custom', NULL, 'role', 4, 'Role', 302, NULL, '2023-04-20 10:53:11', 0);
INSERT INTO `sys_menu` VALUES (7, '菜單管理', '/menu', 'el-icon-menu', NULL, NULL, 4, 'Menu', 303, NULL, NULL, 0);
INSERT INTO `sys_menu` VALUES (10, '主頁(yè)', '/home', 'el-icon-house', '主頁(yè)', NULL, NULL, 'Home', 0, NULL, '2023-04-20 09:45:07', 0);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `role_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '唯一標(biāo)識(shí)',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名稱',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  `is_delete` tinyint(0) NULL DEFAULT 0 COMMENT '是否刪除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '超級(jí)管理員', '煩煩煩', NULL, '2023-04-20 09:32:12', 0);
INSERT INTO `sys_role` VALUES (2, 'user', '普通用戶', NULL, NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (3, 'vip', 'Vip用戶', NULL, NULL, '2023-04-22 00:33:12', 0);

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色id',
  `menu_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜單id',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-菜單-關(guān)聯(lián)表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES ('1', '10', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('1', '4', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('1', '47', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('1', '5', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('1', '6', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('1', '7', '2023-04-21 17:02:29', NULL);
INSERT INTO `sys_role_menu` VALUES ('2', '10', '2023-04-22 12:32:24', NULL);
INSERT INTO `sys_role_menu` VALUES ('2', '4', '2023-04-22 12:32:24', NULL);
INSERT INTO `sys_role_menu` VALUES ('2', '5', '2023-04-22 12:32:24', NULL);
INSERT INTO `sys_role_menu` VALUES ('2', '6', '2023-04-22 12:32:24', NULL);
INSERT INTO `sys_role_menu` VALUES ('3', '10', '2023-04-22 14:13:53', NULL);
INSERT INTO `sys_role_menu` VALUES ('3', '4', '2023-04-22 14:13:53', NULL);
INSERT INTO `sys_role_menu` VALUES ('3', '5', '2023-04-22 14:13:53', NULL);
INSERT INTO `sys_role_menu` VALUES ('3', '6', '2023-04-22 14:13:53', NULL);
INSERT INTO `sys_role_menu` VALUES ('3', '7', '2023-04-22 14:13:53', NULL);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用戶名',
  `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '密碼',
  `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '昵稱',
  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '郵箱',
  `phonenumber` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '電話',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '地址',
  `avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'http://localhost:9090/files/20230409082108000000936.jpg' COMMENT '頭像',
  `is_delete` tinyint(0) NULL DEFAULT 0 COMMENT '是否刪除',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '202cb962ac59075b964b07152d234b70', '無(wú)所謂^_^', '2673152463@qq.com', '2673152463', '浙江省', 'http://localhost:9090/files/2023/04/22/20230422123301000000166.jpg', 0, '2022-01-22 21:10:27', '2023-04-22 00:33:05');
INSERT INTO `sys_user` VALUES (16, 'vip', '202cb962ac59075b964b07152d234b70', '小黑子', '2', '2', '2', 'http://localhost:9090/files/2023/04/22/20230422123303000000698.png', 0, '2022-02-26 22:10:14', '2023-04-22 12:20:29');
INSERT INTO `sys_user` VALUES (17, 'user', '202cb962ac59075b964b07152d234b70', '我是三三哦豁', '3', '2673152463', '3', 'https://profile.yssmx.com/B/7/0/1_weixin_51603038', 0, '2022-02-26 22:10:18', '2023-04-22 12:16:05');
INSERT INTO `sys_user` VALUES (18, 'nzz', '202cb962ac59075b964b07152d234b70', '哪吒', '2', '2', '2', '', 0, '2022-03-29 16:59:44', '2023-04-21 23:16:50');
INSERT INTO `sys_user` VALUES (25, 'sir', '202cb962ac59075b964b07152d234b70', '安琪拉', NULL, NULL, NULL, NULL, 0, '2022-06-08 17:00:47', '2023-04-21 23:16:50');
INSERT INTO `sys_user` VALUES (26, 'err', '202cb962ac59075b964b07152d234b70', '妲己', '11', '1', '1', NULL, 0, '2022-07-08 17:20:01', '2023-04-21 23:10:29');
INSERT INTO `sys_user` VALUES (28, 'ddd', '202cb962ac59075b964b07152d234b70', 'ddd', '', '', '', 'http://localhost:9090/file/7de0e50f915547539db12023cf997276.jpg', 0, '2022-11-09 10:41:07', '2023-04-21 23:10:29');
INSERT INTO `sys_user` VALUES (29, 'ffff', '202cb962ac59075b964b07152d234b70', 'ffff', NULL, NULL, NULL, NULL, 0, '2022-12-10 11:53:31', '2023-04-21 23:10:29');
INSERT INTO `sys_user` VALUES (36, 'aaa', '47bce5c74f589f4867dbd57e9ca9f808', NULL, NULL, NULL, NULL, 'http://localhost:9090/files/20230409082108000000936.jpg', 0, '2023-04-21 22:45:25', '2023-04-21 23:10:16');
INSERT INTO `sys_user` VALUES (37, 'fff', '343d9040a671c45832ee5381860e2996', NULL, NULL, NULL, NULL, 'http://localhost:9090/files/20230409082108000000936.jpg', 0, '2023-04-21 23:02:56', '2023-04-21 23:17:24');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用戶id',
  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色id',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時(shí)間',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用戶-角色關(guān)聯(lián)表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1', '2023-04-20 11:03:24', NULL);
INSERT INTO `sys_user_role` VALUES ('16', '3', '2023-04-22 12:21:54', NULL);
INSERT INTO `sys_user_role` VALUES ('17', '2', '2023-04-22 12:16:05', NULL);
INSERT INTO `sys_user_role` VALUES ('18', '2', '2023-04-22 12:30:26', NULL);
INSERT INTO `sys_user_role` VALUES ('25', '2', '2023-04-22 12:30:29', NULL);
INSERT INTO `sys_user_role` VALUES ('26', '2', '2023-04-22 12:30:34', NULL);
INSERT INTO `sys_user_role` VALUES ('28', '2', '2023-04-22 12:30:36', NULL);
INSERT INTO `sys_user_role` VALUES ('29', '2', '2023-04-22 12:30:39', NULL);
INSERT INTO `sys_user_role` VALUES ('35', '1', '2023-04-20 09:07:19', NULL);
INSERT INTO `sys_user_role` VALUES ('36', '2', '2023-04-22 12:30:41', NULL);
INSERT INTO `sys_user_role` VALUES ('37', '2', '2023-04-22 12:30:45', NULL);

SET FOREIGN_KEY_CHECKS = 1;

四、系統(tǒng)搭建

項(xiàng)目結(jié)構(gòu)

建議小伙伴們?nèi)ノ业膅itee上下載源碼,然后運(yùn)行。因?yàn)榇a有點(diǎn)(優(yōu)點(diǎn))多,不好全部寫(xiě)在博客里??

項(xiàng)目依賴 

<?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>
    <groupId>com.wusuowei</groupId>
    <artifactId>Shiro_Jwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Shiro_Jwt</name>
    <description>Shiro_Jwt</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.6.13</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- md5加密 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.50</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.wusuowei.shiro_jwt_vue.ShiroJwtApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

核心代碼

1.JWT 工具類

主要用來(lái)生成 token、校驗(yàn) token

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.wusuowei.shiro_jwt.model.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JWTUtil {

    //token有效時(shí)長(zhǎng)30分鐘
    private static Long EXPIRE;
    //token的密鑰
    private static String SECRET;
    //refresh-expire續(xù)期過(guò)期時(shí)間
    private static Long REFRESHEXPIRE;

    @Value("${jwt.expire}")
    public void setExpire(Long expire){
        JWTUtil.EXPIRE = expire*1000;
    }

    @Value("${jwt.secret}")
    public void setSecret(String secret){
        JWTUtil.SECRET = secret;
    }

    @Value("${jwt.refresh-expire}")
    public void setRefreshExpire(Long refreshExpire){
        JWTUtil.REFRESHEXPIRE = refreshExpire;
    }
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil2;

    private static RedisUtil redisUtil;

    @PostConstruct
    public void init(){
        JWTUtil.redisUtil = redisUtil2;
    }

    public static String createToken(User user) throws UnsupportedEncodingException {
        //token過(guò)期時(shí)間
        Date date=new Date(System.currentTimeMillis()+EXPIRE);

        //Date now = new Date();
        long now = System.currentTimeMillis();

        //jwt的header部分
        Map<String ,Object> map=new HashMap<>();
        map.put("alg","HS256");
        map.put("typ","JWT");

        //使用jwt的api生成token
        String token= JWT.create()
                .withHeader(map)
                .withClaim("uid", user.getId().toString())//私有聲明
                .withExpiresAt(date)//過(guò)期時(shí)間
                .withIssuedAt(new Date(now))//簽發(fā)時(shí)間
                .sign(Algorithm.HMAC256(SECRET));//簽名
        redisUtil.hset("refresh",String.valueOf(user.getId()),Long.valueOf((long) Math.floor(now/1000)), REFRESHEXPIRE);
        return token;
    }

    //校驗(yàn)token的有效性,1、token的header和payload是否沒(méi)改過(guò);2、沒(méi)有過(guò)期
    public static boolean verify(String token){
        try {
            //解密
            JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        }catch (TokenExpiredException e){
            return true;
        }catch (Exception e) {
            return false;
        }
    }

    public static boolean isJwtExpired(String token){
        /**
         * @desc 判斷token是否過(guò)期
         * @author lj
         */
        try {
            DecodedJWT decodeToken = JWT.decode(token);
            return decodeToken.getExpiresAt().before(new Date());
        } catch(Exception e){
            return true;
        }
    }
    //無(wú)需解密也可以獲取token的信息
    public static String getUserId(String token){
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("uid").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    //無(wú)需解密也可以獲取token的信息
    public static String getAccessToken(String token){
        try {
            DecodedJWT jwt = JWT.decode(token);
            return String.valueOf(jwt.getIssuedAt().getTime()/1000);
        } catch (JWTDecodeException e) {
            return "";
        }
    }

}

2.JWTFilter

主要作用就是攔截請(qǐng)求,判斷請(qǐng)求頭中書(shū)否攜帶 token。如果攜帶,就交給 Realm 處理。

import com.wusuowei.shiro_jwt.model.po.User;
import com.wusuowei.shiro_jwt.shiro.JWTToken;
import com.wusuowei.shiro_jwt.utils.JWTUtil;
import com.wusuowei.shiro_jwt.utils.RedisUtil;
import com.wusuowei.shiro_jwt.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {


    //是否允許訪問(wèn),如果帶有 token,則對(duì) token 進(jìn)行檢查,否則直接通過(guò)
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判斷請(qǐng)求的請(qǐng)求頭是否帶上 "Token"
        if (isLoginAttempt(request, response)) {
            //如果存在,則進(jìn)入 executeLogin 方法執(zhí)行登入,檢查 token 是否正確
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                log.info("認(rèn)證出錯(cuò)");
                responseError(response, e.getMessage()); //這里就不進(jìn)行跳轉(zhuǎn)了,直接全局異常捕獲
            }
        }
        //如果請(qǐng)求頭不存在 Token,則可能是執(zhí)行登陸操作或者是游客狀態(tài)訪問(wèn),無(wú)需檢查 token,直接返回 true
        return true;
    }

    /**
     * 判斷用戶的請(qǐng)求是否為認(rèn)證。
     * 檢測(cè) header 里面是否包含 Token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("是認(rèn)證請(qǐng)求isLoginAttempt");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("token");
        return token != null;
    }

    /*
     * executeLogin實(shí)際上就是先調(diào)用createToken來(lái)獲取token,這里我們重寫(xiě)了這個(gè)方法,就不會(huì)自動(dòng)去調(diào)用createToken來(lái)獲取token
     * 然后調(diào)用getSubject方法來(lái)獲取當(dāng)前用戶再調(diào)用login方法來(lái)實(shí)現(xiàn)登錄
     * 這也解釋了我們?yōu)槭裁匆远xjwtToken,因?yàn)槲覀儾辉偈褂肧hiro默認(rèn)的UsernamePasswordToken了。
     * */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("executeLogin");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String token = req.getHeader("token");
        JWTToken jwt = null;

        String newJwtToken = getNewJwtToken(req, res, token);
        if (newJwtToken != null) {
            token = newJwtToken;
        }

        jwt = new JWTToken(token);
        //交給自定義的realm對(duì)象去登錄,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
        getSubject(request, response).login(jwt);
        return true;
    }

    /**
     * @description token續(xù)期
     * @param request 要求
     * @param response 回答
     * @param token 令牌
     * @return {@link String }
     * @author LGY
     * @date 2023/04/18 19:44
     */
    private String getNewJwtToken(HttpServletRequest request, HttpServletResponse response, String token) throws Exception {
        RedisUtil redisUtil = SpringContextUtils.getBean(RedisUtil.class);
        String uid = null;
        try {
            uid = JWTUtil.getUserId(token);
        } catch (Exception e) {
            throw new AuthenticationException("token非法,不是規(guī)范的token,可能被篡改了");
        }
        if (!JWTUtil.verify(token) || uid == null) {
            throw new AuthenticationException("token認(rèn)證失效,token錯(cuò)誤或者過(guò)期,請(qǐng)重新登陸");
        }

        String refreshToken = String.valueOf(redisUtil.hget("refresh",uid));
        String accessToken = JWTUtil.getAccessToken(token);

        if (StringUtils.isBlank(refreshToken) || !accessToken.equals(refreshToken)) {
            throw new AuthenticationException("token過(guò)期,請(qǐng)重新登陸");
        }

        //token續(xù)期
        if (JWTUtil.isJwtExpired(token) && accessToken.equals(refreshToken)) {
            //生成新token
            User user = new User();
            user.setId(Integer.valueOf(uid));
            token = JWTUtil.createToken(user);
            log.info("token續(xù)期成功:" + token);
            response.addHeader("refreshtoken", token);
            response.setHeader("Access-Control-Expose-Headers", "refreshtoken");
            return token;
        }
        return null;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("preHandle");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
        // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài)
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 將非法請(qǐng)求跳轉(zhuǎn)到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        System.out.println("responseError");

        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //設(shè)置編碼,否則中文字符在重定向時(shí)會(huì)變?yōu)榭兆址?
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

3.JwtToken

shiro 在沒(méi)有和 jwt 整合之前,用戶的賬號(hào)密碼被封裝成了 UsernamePasswordToken 對(duì)象,UsernamePasswordToken 其實(shí)是 AuthenticationToken 的實(shí)現(xiàn)類。這里既然要和 jwt 整合,JWTFilter 傳遞給 Realm 的 token 必須是 AuthenticationToken 的實(shí)現(xiàn)類。

import org.apache.shiro.authc.AuthenticationToken;

public class JWTToken implements AuthenticationToken {

    private String token;

    public JWTToken(String token){
        this.token=token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

4.自定義 Realm

繼承 AuthorizingRealm從數(shù)據(jù)庫(kù)中讀取用戶數(shù)據(jù),實(shí)現(xiàn)認(rèn)證和授權(quán)兩個(gè)方法

import com.wusuowei.shiro_jwt.model.po.Menu;
import com.wusuowei.shiro_jwt.model.po.Role;
import com.wusuowei.shiro_jwt.model.po.User;
import com.wusuowei.shiro_jwt.service.MenuService;
import com.wusuowei.shiro_jwt.service.RoleService;
import com.wusuowei.shiro_jwt.service.UserService;
import com.wusuowei.shiro_jwt.utils.JWTUtil;
import com.wusuowei.shiro_jwt.utils.RedisUtil;
import org.apache.commons.lang3.StringUtils;
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 org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Component
public class MyRealm extends AuthorizingRealm {

    @Value("${jwt.refresh-expire}")
    //refresh-expire續(xù)期過(guò)期時(shí)間
    private Long REFRESHEXPIRE;
    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;


    @Autowired
    private RedisUtil redisUtil;

    //根據(jù)token判斷此Authenticator是否使用該realm
    //必須重寫(xiě)不然shiro會(huì)報(bào)錯(cuò)
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 只有當(dāng)需要檢測(cè)用戶權(quán)限的時(shí)候才會(huì)調(diào)用此方法,例如@RequiresRoles,@RequiresPermissions之類的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授權(quán)~~~~~");
        User user = (User) principals.getPrimaryPrincipal();
        String uid = String.valueOf(user.getId());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        List<Role> redisRoles = (List<Role>) redisUtil.hget("userPower", "roles:"+uid);
        List<Menu> redisPermissions = (List<Menu>) redisUtil.hget("userPower", "permission:"+uid);
        if (redisRoles != null && redisPermissions != null) {
            info.addRoles(redisRoles.stream().map(Role::getRoleKey).collect(Collectors.toSet()));
            info.addStringPermissions(redisPermissions.stream().filter(item -> StringUtils.isNotBlank(item.getPermission())).map(Menu::getPermission).collect(Collectors.toSet()));
            return info;
        }
        //查詢數(shù)據(jù)庫(kù)來(lái)獲取用戶的角色
        List<Role> roles = roleService.getRoles(uid);
        info.addRoles(roles.stream().map(Role::getRoleKey).collect(Collectors.toSet()));

        //查詢數(shù)據(jù)庫(kù)來(lái)獲取用戶的權(quán)限
        List<Menu> permissions = menuService.getPermissionByUid(uid);
        Set<String> collect = permissions.stream().filter(item -> StringUtils.isNotBlank(item.getPermission())).map(Menu::getPermission).collect(Collectors.toSet());
        info.addStringPermissions(collect);
        redisUtil.hset("userPower", "roles:" + uid, roles, REFRESHEXPIRE);
        redisUtil.hset("userPower", "permissions:" + uid, collect, REFRESHEXPIRE);
        return info;
    }


    /**
     * 默認(rèn)使用此方法進(jìn)行用戶名正確與否驗(yàn)證,錯(cuò)誤拋出異常即可,在需要用戶認(rèn)證和鑒權(quán)的時(shí)候才會(huì)調(diào)用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("認(rèn)證~~~~~~~");
        String jwt = (String) token.getCredentials();
        String uid = JWTUtil.getUserId(jwt);
        User redisUser = (User) redisUtil.get("userInfo:" + uid);
        if (redisUser != null) {
            return new SimpleAuthenticationInfo(redisUser, jwt, "MyRealm");
        }
        User user = userService.getById(uid);
        if (user == null) {
            throw new AuthenticationException("該用戶不存在");
        }
        redisUtil.hset("userInfo",uid, user, REFRESHEXPIRE);
        return new SimpleAuthenticationInfo(user, jwt, "MyRealm");
    }
}

5.配置Shiro

ShiroConfig主要配置了:過(guò)濾器、安全管理器和不進(jìn)行攔截的路徑,比如登錄

import com.wusuowei.shiro_jwt.filter.JWTFilter;
import com.wusuowei.shiro_jwt.shiro.MyRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設(shè)置自定義 realm.
        securityManager.setRealm(myRealm);

        //關(guān)閉session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    /**
     * 先走 filter ,然后 filter 如果檢測(cè)到請(qǐng)求頭存在 token,則用 token 去 login,走 Realm 去驗(yàn)證
     */
    @Bean
    public ShiroFilterFactoryBean factory(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);

        // 添加自己的過(guò)濾器并且取名為jwt
        LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>();
        //設(shè)置我們自定義的JWT過(guò)濾器
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);

        // 設(shè)置無(wú)權(quán)限時(shí)跳轉(zhuǎn)的 url;
        factoryBean.setUnauthorizedUrl("/unauthorized/無(wú)權(quán)限");

        LinkedHashMap<String, String> filterRuleMap = new LinkedHashMap<>();
        // 訪問(wèn) /unauthorized/** 不通過(guò)JWTFilter
        filterRuleMap.put("/unauthorized/**", "anon");
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/register", "anon");
        filterRuleMap.put("/check", "anon");
        filterRuleMap.put("/files/**", "anon");
        filterRuleMap.put("/test2", "anon");
      //  filterRuleMap.put("/logout", "anon");
        // 所有請(qǐng)求通過(guò)我們自己的JWT Filter
        filterRuleMap.put("/**", "jwt");

        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 添加注解支持,如果不加的話很有可能注解失效
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {

        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    

}

五、項(xiàng)目核心邏輯介紹

1.jwt無(wú)狀態(tài)登錄

在微服務(wù)中我們一般采用的是無(wú)狀態(tài)登錄,而傳統(tǒng)的session方式,在前后端分離的微服務(wù)架構(gòu)下,如繼續(xù)使用則必須要解決跨域sessionId問(wèn)題、集群session共享問(wèn)題等等。這顯然是費(fèi)力不討好的事,而整合shiro,它的默認(rèn)實(shí)現(xiàn)就是通過(guò)session的方式。
原因:   
(1)shiro默認(rèn)的攔截跳轉(zhuǎn)都是跳轉(zhuǎn)url頁(yè)面,而前后端分離后,后端并無(wú)權(quán)干涉頁(yè)面跳轉(zhuǎn)。   
(2)shiro默認(rèn)使用的登錄攔截校驗(yàn)機(jī)制恰恰就是使用的session。 

解決:在這個(gè)系統(tǒng)中,我通過(guò)在ShiroConfig中配置filterRuleMap.put("/login", "anon");放行登錄請(qǐng)求,然后登錄成功后生成token并返回給前端,之后前端的每次請(qǐng)求都攜帶這個(gè)token,后端的JWTFilter進(jìn)行過(guò)濾判斷,然后通過(guò)調(diào)用getSubject(request, response).login(jwt);交給MyRealm進(jìn)行認(rèn)證、授權(quán)。

2.token可控

為什么要token可控。因?yàn)槿绻脩舻卿浐脦状?,拿到很多token,用戶就可以通過(guò)這些token(沒(méi)有過(guò)期且正確)中的任意一個(gè)進(jìn)行訪問(wèn)。但是如果我們想控制用戶的登錄,實(shí)現(xiàn)一些功能,比如讓能統(tǒng)計(jì)在線人數(shù),就需要實(shí)現(xiàn)token的可控。

解決:登錄認(rèn)證通過(guò)后返回AccessToken信息(在AccessToken中保存當(dāng)前的時(shí)間和用戶id),同時(shí)在Redis中設(shè)置一條Key為用戶id,Value為當(dāng)前時(shí)間戳(登錄時(shí)間和token中的一樣)的RefreshToken

核心代碼在JWTUtil的createToken方法中

//使用jwt的api生成token
        String token= JWT.create()
                .withHeader(map)
                .withClaim("uid", user.getId().toString())//私有聲明
                .withExpiresAt(date)//過(guò)期時(shí)間
                .withIssuedAt(new Date(now))//簽發(fā)時(shí)間
                .sign(Algorithm.HMAC256(SECRET));//簽名
        redisUtil.hset("refreshToken",String.valueOf(user.getId()),Long.valueOf((long) Math.floor(now/1000)), REFRESHTOKENEXPIRE);

現(xiàn)在認(rèn)證時(shí)必須AccessToken沒(méi)被篡改過(guò)以及Redis存在所對(duì)應(yīng)的RefreshToken,且RefreshToken時(shí)間戳和AccessToken信息中時(shí)間戳一致才算認(rèn)證通過(guò),這樣可以做到JWT的可控性,如果重新登錄獲取了新的AccessToken,舊的AccessToken就認(rèn)證不了,因?yàn)镽edis中所存放的的RefreshToken時(shí)間戳信息只會(huì)和最新的AccessToken信息中攜帶的時(shí)間戳一致,這樣每個(gè)用戶就只能使用最新的AccessToken認(rèn)證。

核心代碼在JWTFilter中的getNewJwtToken方法中

if (!JWTUtil.verify(token) || uid == null) {
            throw new AuthenticationException("token認(rèn)證失效,請(qǐng)重新登陸");
}

String refreshToken = String.valueOf(redisUtil.hget("refreshToken",uid));
String accessToken = JWTUtil.getAccessToken(token);

if (StringUtils.isBlank(refreshToken) || !accessToken.equals(refreshToken)) {
    throw new AuthenticationException("token過(guò)期,請(qǐng)重新登陸");
}

3.token續(xù)期

如果用戶正在訪問(wèn)我們的網(wǎng)站,突然token過(guò)期了,這時(shí)用戶只能重新登錄獲取新的token進(jìn)行訪問(wèn),這樣的用戶體驗(yàn)肯定不好。

解決:1. 本身AccessToken的過(guò)期時(shí)間為5分鐘,RefreshToken過(guò)期時(shí)間為30分鐘,當(dāng)?shù)卿浐髸r(shí)間過(guò)了5分鐘之后,當(dāng)前AccessToken便會(huì)過(guò)期失效,再次帶上AccessToken訪問(wèn)判斷是否過(guò)期,如果過(guò)期,開(kāi)始判斷是否要進(jìn)行AccessToken刷新,首先redis查詢RefreshToken是否存在,以及時(shí)間戳和過(guò)期AccessToken所攜帶的時(shí)間戳是否一致,如果存在且一致就進(jìn)行AccessToken刷新。

2. 刷新后新的AccessToken過(guò)期時(shí)間依舊為5分鐘,時(shí)間戳為當(dāng)前最新時(shí)間戳,同時(shí)也設(shè)置RefreshToken中的時(shí)間戳為當(dāng)前最新時(shí)間戳,刷新過(guò)期時(shí)間重新為30分鐘過(guò)期,最終將刷新的AccessToken設(shè)置到在Response的Header中的refreshToken字段返回。

3. 同時(shí)前端進(jìn)行獲取替換,下次用新的AccessToken進(jìn)行訪問(wèn)即可。

核心代碼還是在JWTFilter中的getNewJwtToken方法中

 //token續(xù)期
if (JWTUtil.isJwtExpired(token) && accessToken.equals(refreshToken)) {
   //生成新token
   User user = new User();
   user.setId(Integer.valueOf(uid));
   token = JWTUtil.createToken(user);
   log.info("token續(xù)期成功:" + token);
   response.addHeader("refreshToken", token);
   response.setHeader("Access-Control-Expose-Headers", "refreshToken");
   return token;
}

4.redis緩存數(shù)據(jù)

在MyRealm中用Redis對(duì)認(rèn)證、授權(quán)數(shù)據(jù)進(jìn)行緩存,不然每次請(qǐng)求都會(huì)去查詢數(shù)據(jù)庫(kù)。


小結(jié)

本文的所有源碼包含前端我都放在我的gitee上了,大家可以下載下來(lái)作為自己項(xiàng)目的后臺(tái)管理系統(tǒng)。下一篇我會(huì)用這個(gè)系統(tǒng)實(shí)現(xiàn)各種文件的上傳下載預(yù)覽,包括分片上傳和斷點(diǎn)續(xù)傳,具體會(huì)整合minio和kkViewFile。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-457691.html

到了這里,關(guān)于從零開(kāi)發(fā)一個(gè)自己的Shiro+Vue通用后臺(tái)管理系統(tǒng)(附源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包