包含前后端技術(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)行
項(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ù)中。
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ō)。
3.用戶訪問(wèn)需要認(rèn)證的資源時(shí),需要進(jìn)行token校驗(yàn)和續(xù)期判斷
三、數(shù)據(jù)庫(kù)
E-R圖設(shè)計(jì)
沒(méi)有加外鍵,因?yàn)樵黾訒?huì)造成數(shù)據(jù)庫(kù)壓力。實(shí)體表都加入了邏輯刪除字段。
數(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ù)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-457691.html
小結(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)!