04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期

1. 登录后获取用户信息
非常好实现. 只要新建一个controller, 并调用SS提供的Authentication对象即可
package com.sunsplanter.controller;@RestController
public class UserController {@GetMapping(value = "api/login/info")public R loginInfo(Authentication authentication) {TUser tUser = (TUser)authentication.getPrincipal();return R.OK(tUser);}}
未登录状态下可以直接访问 api/login/info吗?
不可以. 因为在安全配置类已经写明了, 仅登陆界面允许任何人访问, 其他所有界面都需要认证
<由于未写JWT, 默认使用Session 保存会话,> ???好像不对
因此只要我们先通过登录接口登录, 然后再直接访问获取用户信息接口即可

2. 使用JWT打通登录后各个页面的认证
前后端分离的项目一般会使用token(jwt)实现登录状态的保持;(java web : session)
token其实就是一个随机字符串(字符串要求是唯一的,不同人的token都不能相同),当用户在登录页面输入账号和密码后,前端将账号密码发送给后端,后端检验完账号和密码后,会生成一个随机不重复的字符串即(token),并将其响应给前端,前端拿到token后,需要在客户端进行持久化存储(一般会写在localStorage或者sessionStorage中),那么下次在向后端数据接口发送请求的时候,一般需要将token一并发送给后端数据接口,后端数据接口会对token进行校验,如果合法则正常响应请求,如果不合法,则提示未登录。
2.1 sessionStorage与localStorage
它们是javascript对象,浏览器支持这两个对象,可以直接使用. 属于前端范畴
localStorage和sessionStorage都是用来在浏览器客户端存储临时信息的对象;
sessionStorage、localStorage区别?
sessionStorage只在一个浏览器页面有效,比如你打开一个新的tab浏览器页会失效,你关闭浏览器后,再打开浏览器也会失效;
localStorage在整个浏览器中都有效,重启浏览器也有效;除非你手动删除了localStorage,才会失效;
2.2 修改登录成功拦截器
根据流程图, 在查询到对象并返回给SS后, SS会调用成功拦截器,
就在这个拦截器中, 根据查询到的对象生成JWT并同时存进Redis和返回前端.
//登录成功会自动执行这个类中的onAuthenticationSuccess方法, 该方法返回自定义的Json给前端
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Resourceprivate RedisService redisService;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//登录成功,执行该方法,在该方法中返回json给前端,就行了TUser tUser = (TUser) authentication.getPrincipal();//1.生成JWT//由于createJWT方法定义的参数是序列化后的对象, 因此先调用JSONUtils序列化对象String userJSON = JSONUtils.toJSON(tUser);String jwt = JWTUtils.createJWT(userJSON);//2.写入RedisredisService.setValue(Constants.REDIS_JWT_KEY + tUser.getId(), jwt);//3. 设置JWT的过期时间(如果选择记住我, 过期时间是7天, 否则30分钟)String rememberMe = request.getParameter("rememberMe");//勾选了记住我就设置为7天, 其中EXPIRE_TIME就是7天,DEFAULT_EXPIRE_TIME是半小时if (Boolean.parseBoolean(rememberMe)){redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}//登录成功的统一结果R result = R.OK(jwt);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);}
}
常量类为
package com.sunsplanter.constant;/*** 常量类*/
public class Constants {public static final String LOGIN_URI = "/api/login";//redis的key的命名规范: 项目名:模块名:功能名:唯一业务参数(比如用户id)public static final String REDIS_JWT_KEY = "dlyk:user:login:";//redis中负责人的keypublic static final String REDIS_OWNER_KEY = "dlyk:user:owner";//jwt过期时间7天public static final Long EXPIRE_TIME = 7 * 24 * 60 * 60L;//jwt过期时间30分钟public static final Long DEFAULT_EXPIRE_TIME = 30 * 60L;
}
redis类 为
package com.sunsplanter.servicepublic interface RedisService {void setValue(String key, Object value);Object getValue(String key);Boolean removeValue(String key);//给jwt设置过期时间Boolean expire(String key, Long timeOut, TimeUnit timeUnit);
}
package com.sunsplanter.service.impl;@Service
public class RedisServiceImpl implements RedisService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void setValue(String key, Object value) {redisTemplate.opsForValue().set(key, value);}@Overridepublic Object getValue(String key) {return redisTemplate.opsForValue().get(key);}@Overridepublic Boolean removeValue(String key) {return redisTemplate.delete(key);}//给JWT设置过期时间@Overridepublic Boolean expire(String key, Long timeOut, TimeUnit timeUnit) {return redisTemplate.expire(key, timeOut, timeUnit);}
}
在网页登录后可以看到已经存到了localstorage

3 Filter验证Token
如流程图所示, 第一次登录成功过后, 往后每次登录会携带token登录,
因此, 在config.filter文件夹下新建一个token过滤器类, 该类实现OncePerRequestFilter
并在SS的安全配置类中中添加进去
package com.sunsplanter.config.filter;@Component
public class TokenVerifyFilter extends OncePerRequestFilter{@Resourceprivate RedisService redisService;//spring boot框架的ioc容器中已经创建好了该线程池,可以注入直接使用@Resourceprivate ThreadPoolTaskExecutor threadPoolTaskExecutor;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if (request.getRequestURI().equals(Constants.LOGIN_URI)) { //如果是登录请求,此时还没有生成jwt,那不需要对登录请求进行jwt验证//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个FilterfilterChain.doFilter(request, response);} else {String token = null;if (request.getRequestURI().equals(Constants.EXPORT_EXCEL_URI)) {//从请求路径的参数中获取tokentoken = request.getParameter("Authorization");} else {//其他请求都是从请求头中获取tokentoken = request.getHeader("Authorization");}if (!StringUtils.hasText(token)) {//token验证未通过的统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//验证token有没有被篡改过if (!JWTUtils.verifyJWT(token)) {//token验证未通过统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}TUser tUser = JWTUtils.parseUserFromJWT(token);String redisToken = (String) redisService.getValue(Constants.REDIS_JWT_KEY + tUser.getId());//验证token非空if (!StringUtils.hasText(redisToken)) {//token验证未通过统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_EXPIRED);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//验证token是否与redis中的一致if (!token.equals(redisToken)) {//token验证未通过的统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_NONE_MATCH);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//jwt验证通过了,那么在spring security的上下文环境中要设置一下,设置当前这个人是登录过的,你后续不要再拦截他了UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser, tUser.getLoginPwd(), tUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//刷新一下token(异步处理,new一个线程去执行)/*new Thread(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}}).start();*///异步处理(更好的方式,使用线程池去执行)threadPoolTaskExecutor.execute(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}});//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个FilterfilterChain.doFilter(request, response);}}
}
4. token续期
现在一个问题是
token一旦发布, 是7天/30分钟 .
原来没有续期时 , token只能获得与提前删除.
然而一个现实需求是
假如用户获得是30分钟的token. 在这三十分钟内, 只要用户有任何操作, 我们应当自动刷新token到30分钟 , 否则假如不管用户怎么操作, token都固定30分钟刷新 , 这显然不符合逻辑
目标为, 任何除登出的用户登录都会重置token过期时间
然后是代码放在哪里的问题
我们知道, 即使登录过后 , 我们在每一次操作时仍会用TokenVerifyFilter检查token的有效期.
因此直接把续期代码放到该类中即可, 每次有操作->执行TokenVerifyFilter检查token有效期->有效则续期token
在TokenVerifyFilter中新增刷新代码
//刷新一下token(异步处理,new一个线程去执行)
//没必要因为续期token而阻塞整个进程, 毕竟此时已经校验过了不影响本次执行new Thread(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}}).start();
相关文章:
04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期
1. 登录后获取用户信息 非常好实现. 只要新建一个controller, 并调用SS提供的Authentication对象即可 package com.sunsplanter.controller;RestController public class UserController {GetMapping(value "api/login/info")public R loginInfo(Authentication a…...
RISC-V知识总结 —— 指令集
资源1: RISC-V China – RISC-V International 资源2: RISC-V International – RISC-V: The Open Standard RISC Instruction Set Architecture 资源3: RV32I, RV64I Instructions — riscv-isa-pages documentation 1. 指令集架构的类型 在讨论RISC-V或任何处理器架构时&…...
基于Java jsp+mysql+Spring的汽车出租平台租赁网站平台设计和实现
基于Java jspmysqlSpring的汽车出租平台租赁网站平台设计和实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留…...
[AutoSar]BSW_Com 01 Can通信入门
目录 关键词平台说明一、车身CAN简介二、相关模块三、Can报文分类及信号流路径3.1 应用报文3.2 应用报文(多路复用multiplexer)3.3 诊断报文3.4 网络管理报文3.5 XCP报文(标定报文) 关键词 嵌入式、C语言、autosar、OS、BSW 平台…...
离散数学 第七单元 tree
目录 树的定义 树的特点 Spanning Tree 生成树(重要!) 生成树算法 DFS 深度优先 BFS 广度优先 Minimun Spanning Tree 最小生成树 Kruscal算法 Prim算法 根树 根数的遍历 前序遍历 中序遍历 后序遍历 表达式的二叉树 中缀…...
基于MPPT最大功率跟踪算法的涡轮机控制系统simulink建模与仿真
目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于MPPT最大功率跟踪算法的涡轮机控制系统simulink建模与仿真.mppt采用爬山法实现,仿真输出MPPT控制效果,功率,转速等。 2.系统仿真结果 …...
Hbase和Clickhouse对比简单总结
Hbase和Clickhouse是两种不同的数据库系统,它们各自适用于不同的场景。以下是两者之间的对比: 数据模型: HBase 是一种基于列的存储系统,它适合处理大规模的数据集,特别是那些需要快速随机访问的场景。ClickHouse 则是…...
Spring基础之AOP和代理模式
文章目录 理解AOPAOP的实现原理 AOP代理模式静态代理动态代理1-JDK动态代理2-CGLIB动态代理 总结 理解AOP OOP - - Object Oriented Programming 面向对象编程 AOP - - Aspect Oriented Programming 面向切面编程 AOP是Spring提供的关键特性之一。AOP即面向切面编程࿰…...
二层交换机和三层交换机区别
01、二层交换机 二层交换机,也被称为数据链路层交换机,是在OSI模型的数据链路层(第二层)进行数据交换的设备。它基于MAC(Media Access Control)地址来转发数据包,实现局域网内部的数据传输 1、…...
【Java程序设计】【C00267】基于Springboot的在线考试系统(有论文)
基于Springboot的在线考试系统(有论文) 项目简介项目获取开发环境项目技术运行截图 项目简介 本系统是基于Springboot的在线考试系统;本系统主要分为管理员、教师和学生三种角色; 管理员登录系统后,可以对首页&#x…...
【LeetCode】416. 分割等和子集(中等)——代码随想录算法训练营Day41
题目链接:416. 分割等和子集 题目描述 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums [1,5,11,5] 输出:true 解释&#x…...
51单片机学习(4)-----独立按键进一步控制LED灯
前言:感谢您的关注哦,我会持续更新编程相关知识,愿您在这里有所收获。如果有任何问题,欢迎沟通交流!期待与您在学习编程的道路上共同进步。 目录 一. 独立按键灵活控制LED 程序一:单个独立按键控制多个…...
Redis 学习笔记 3:黑马点评
Redis 学习笔记 3:黑马点评 准备工作 需要先导入项目相关资源: 数据库文件 hmdp.sql后端代码 hm-dianping.zip包括前端代码的 Nginx 启动后端代码和 Nginx。 短信登录 发送验证码 PostMapping("code") public Result sendCode(RequestP…...
电脑恢复删除数据的原理和方法
在恢复数据的时候,很多人都会问,为什么删除的数据还能恢复?本篇和大家一起了解下硬盘上数据的存储方式,文件被删除的时候具体发生了什么,帮助大家理解数据恢复的基本原理。最后还会分享一个好用的数据恢复工具并附上图…...
SpringBoot和SpringCloud的区别,使用微服务的好处和缺点
SpringBoot是一个用于快速开发单个Spring应用程序的框架,通过提供默认配置和约定大于配置的方式,快速搭建基于Spring的应用。让程序员更专注于业务逻辑的编写,不需要过多关注配置细节。可以看成是一种快速搭建房子的工具包,不用从…...
32单片机基础:GPIO输出
目录 简介: GPIO输出的八种模式 STM32的GPIO工作方式 GPIO支持4种输入模式: GPIO支持4种输出模式: 浮空输入模式 上拉输入模式 下拉输入模式 模拟输入模式: 开漏输出模式:(PMOS无效,就…...
【linux】查看openssl程序的安装情况
【linux】查看openssl程序的安装情况 1、查看安装包信息 $ rpm -qa |grep openssl 2、安装路径 $ rpm -ql openssl $ rpm -ql openssl-libs $ rpm -ql openssl-devel 3、相关文件和目录 /usr/bin/openssl /usr/include/openssl /usr/lib64/libssl.so.* /usr/lib64/libcrypto…...
高防服务器主要运用在哪些场景?
高防服务器主要是用来防御DDOS攻击的服务器,能够为客户提供安全维护,高防服务器能够帮助网站拒绝服务攻击,定时扫描网络主节点,进行查找可能会出现的安全漏洞的服务类型,高防服务器也会根据不同的IDC机房环境来提供硬防…...
Eureka:微服务中的服务注册与发现机制
引言 在微服务架构中,由于服务数量巨大并且各个服务的实例可能会频繁上下线,因此服务注册和发现机制至关重要。 那么,有什么工具或技术可以帮助我们解决这个问题呢? 答案就是Eureka。 一、Eureka简介 Eureka是Netflix公司开源的…...
python程序设计基础:字符串与正则表达式
第四章:字符串与正则表达式 4.1字符串 最早的字符串编码是美国标准信息交换码ASCII,仅对10个数字、26个大写英文字母、26个小写英文字母及一些其他符号进行了编码。ASCII码采用1个字节来对字符进行编码,最多只能表示256个符号。 随着信息技…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
