当前位置: 首页 > news >正文

jwttoken+redis+springsecurity

思路

jwttoken不设置过期时间
redis管理过期时间,并且续签
redis中key="login:"+userId, value=jwtUser
再次访问时,解析token中userId,并且根据过期时间自动续签

JWT 实现登录认证 + Token 自动续期方案
pom文件配置

<!--Redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.5.RELEASE</version>
</dependency><!--JWT-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.10.7</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.10.7</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.10.7</version><scope>runtime</scope>
</dependency><!--spring security-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

application.yml配置

spring:redis:database: 0host: redis_ipport: redis_portconnect-timeout: 30000lettuce:pool:# 最大阻塞等待时间,负数表示没有限制max-wait: -1# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0# 连接池中最大连接数,负数表示没有限制max-active: 50password: redis_password

service层logon方法

@Override
public Object login(LoginDTO loginDTO) {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());Authentication authenticate;try {// 对登录用户进行认证authenticate = authenticationManager.authenticate(authenticationToken);} catch (AuthenticationException ex) {throw new ServiceException(ex.getMessage());}if (Objects.isNull(authenticate)) {throw new ServiceException("登录失败");}JWTUser jwtUser = (JWTUser)authenticate.getPrincipal();if(jwtUser.getUserDO().getDeleteFlag()!=0){throw new ServiceException("用户已被停用,如需开启,请联系管理员");}LoginSuccessDTO loginSuccessDTO = new LoginSuccessDTO();loginSuccessDTO.setUserId(jwtUser.getId());loginSuccessDTO.setUsername(jwtUser.getUsername());loginSuccessDTO.setNickName(jwtUser.getUserDO().getNickName());loginSuccessDTO.setUserRealName(jwtUser.getUserDO().getUserRealName());loginSuccessDTO.setDepartment(jwtUser.getUserDO().getDepartment());Map<String, Object> userInfo = DataConvert.BeanPropertyNameTraverse(loginSuccessDTO);String token = JwtUtils.generateToken(userInfo);String userId = jwtUser.getId();Date expirationTime = JwtUtils.getExpirationTime();redisUtils.set("login:" + userId, jwtUser, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);loginSuccessDTO.setLoginTime(new Date());loginSuccessDTO.setExpireTime(expirationTime);loginSuccessDTO.setToken(token);return loginSuccessDTO;
}
@Override
public Object logout() {/** 获取SecurityContextHolder中的userId。* 注销操作也是需要携带token的,spring security已经在内部对token进行验证,* 并能够从redis取出用户信息*/UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();JWTUser jwtUser = (JWTUser)authentication.getPrincipal();String userId = jwtUser.getId();redisUtils.del("login:"+userId);return "注销成功";
}

token工具类

package cma.sxqxgxw.utils.jwt;import cma.sxqxgxw.auth.constant.AuthConstant;
import cma.sxqxgxw.common.exception.ServiceException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;import javax.crypto.SecretKey;
import java.util.Map;
import java.util.Date;public class JwtUtils {private static final String SIGNATURE = "!QW@f5g%T^U&f3r32523534634634654754ygrgfdjjuhfdsdf6";/*** 计算token的过期时间** @return 过期时间*/public static Date getExpirationTime() {return new Date(System.currentTimeMillis() + AuthConstant.EXPIRATION_TIME_IN_SECOND * 1000);}/*** 生成Token* @param claims 用户信息* @return token*/public static String generateToken(Map<String, Object> claims) {Date createdTime = new Date();//Date expirationTime = getExpirationTime();byte[] keyBytes = SIGNATURE.getBytes();SecretKey key = Keys.hmacShaKeyFor(keyBytes);return Jwts.builder().setClaims(claims).setIssuedAt(createdTime)//.setExpiration(expirationTime).signWith(key, SignatureAlgorithm.HS256).compact();}/*** 从token中获取claim** @param token token* @return claim*/public static Claims getClaimsFromToken(String token) {try {return Jwts.parser().setSigningKey(SIGNATURE.getBytes()).parseClaimsJws(token).getBody();} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {throw new ServiceException("Token invalided.");}}/*** 获取token的过期时间** @param token token* @return 过期时间*/public static Date getExpirationDateFromToken(String token) {return getClaimsFromToken(token).getExpiration();}/*** 判断token是否过期** @param token token* @return 已过期返回true,未过期返回false*/public static Boolean isTokenExpired(String token) {Date expiration = getExpirationDateFromToken(token);return expiration.before(new Date());}/*** 判断token是否非法** @param token token* @return 未过期返回true,否则返回false*/public static Boolean validateToken(String token) {System.out.println("token valid");return !isTokenExpired(token);}
}

redis工具类

@Slf4j
@Component
public class RedisUtils {private final RedisTemplate<String, Object> redisTemplate;@Autowiredpublic RedisUtils(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 指定缓存失效时间** @param key  键* @param time 时间(秒)* @return*/public boolean expire(String key, long time) {try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间** @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {log.info("Redis连接失败,可能是Redis服务未启动。" + e.getMessage());return false;}}/*** 删除缓存** @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public boolean del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {Boolean delete = redisTemplate.delete(key[0]);return delete;} else {Long delete = redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));return true;}}return false;}//============================String=============================/*** 普通缓存获取** @param key 键* @return 值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key   键* @param value 值* @return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {log.info("插入操作时,Redis连接失败,可能是Redis服务未启动。" + e.getMessage());return false;}}/*** 普通缓存放入并设置时间** @param key   键* @param value 值* @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time, TimeUnit timeUnit) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, timeUnit);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}
}

jwt过滤器
生成的token中不带有过期时间,token的过期时间由redis进行管理
当redis过期时间小于10分钟时,redis过期时间续签30分钟

@Component
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {private final RedisUtils redisUtils;@Autowiredpublic JWTAuthorizationFilter(AuthenticationManager authenticationManager, RedisUtils redisUtils) {super(authenticationManager);this.redisUtils = redisUtils;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String token = request.getHeader("token");if (!StringUtils.hasText(token)) {request.setAttribute("errorMessage", "Token缺失,请检查请求Header");chain.doFilter(request, response);return;}try {Claims claims = JwtUtils.getClaimsFromToken(token);String userId = (String) claims.get("userId");boolean hasKey = redisUtils.hasKey("login:" + userId);if (!hasKey) {request.setAttribute("errorMessage", "Token无效或已经过期");throw new ServiceException("Token无效或已经过期");}// 令牌续签long expire = redisUtils.getExpire("login:" + userId);System.out.println("剩余时间: "+expire);if (expire < AuthConstant.RENEWAL_TIME_IN_SECOND) {redisUtils.expire("login:" + userId, AuthConstant.EXPIRATION_TIME_IN_SECOND);System.out.println("令牌续签" + AuthConstant.EXPIRATION_TIME_IN_SECOND + "秒");}JWTUser jwtUser = (JWTUser) redisUtils.get("login:" + userId);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());// 设置认证状态SecurityContextHolder.getContext().setAuthentication(authenticationToken);chain.doFilter(request, response);} catch (ServiceException e) {request.setAttribute("errorMessage", e.getMessage());chain.doFilter(request, response);}}
}

过期时间常量

public class AuthConstant {/*** 令牌有效时长*/public static final Long EXPIRATION_TIME_IN_SECOND = 60*30L;/*** 过期时间剩余多少时间时续签*/public static final Long RENEWAL_TIME_IN_SECOND = 60*10L;
}

Redis配置类

package cma.sxqxgxw.common.config.redis;import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** Redis配置* 主要指定自定义序列化器,避免序列化反序列化失败*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 参照StringRedisTemplate内部实现指定序列化器redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置key的序列化器redisTemplate.setKeySerializer(keySerializer());redisTemplate.setHashKeySerializer(keySerializer());// 设置value的序列化器// 解决autoType is not support.xx.xx的问题String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);redisTemplate.setValueSerializer(serializer);redisTemplate.setHashValueSerializer(serializer);return redisTemplate;}private RedisSerializer<String> keySerializer(){return new StringRedisSerializer();}//使用Jackson序列化器private RedisSerializer<Object> valueSerializer(){return new GenericJackson2JsonRedisSerializer();}
}

Jwt拦截器

package cma.sxqxgxw.common.interceptor;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class JwtInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {return true;}}

相关文章:

jwttoken+redis+springsecurity

思路 jwttoken不设置过期时间 redis管理过期时间&#xff0c;并且续签 redis中key"login:"userId, valuejwtUser 再次访问时&#xff0c;解析token中userId&#xff0c;并且根据过期时间自动续签JWT 实现登录认证 Token 自动续期方案 pom文件配置 <!--Redis--&…...

asp.net会议预约管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 会议预约管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 asp.net 会议预约管理系统 二、…...

十六、【橡皮擦工具组】

文章目录 橡皮擦背景橡皮擦1. 一次取样2. 连续取样3. 取样背景色板 魔术橡皮擦 橡皮擦 橡皮擦跟我们平常生活中所用的橡皮擦是一样&#xff0c;它是将图层的内容擦除,只剩下空白部分。另外当我们按住Alt的键去擦除空白部分的时候&#xff0c;也可以将背景的部分显示出来。 另…...

BAT学习——计算当前路径下指定格式文件的MD5值,将文件名与MD5值写入文本

将以下内容粘贴到bat脚本中&#xff0c;双击运行&#xff0c;生成MD5.txt文件&#xff0c;需要其他格式&#xff0c;修改循环中的.rar 多个格式使用or分开 *.rar or *.bat脚本内容 echo off setlocal enabledelayedexpansionrem 设置输出文件名 set "outputFileMD5.txt&…...

排序算法(stable_sort(), sort())

sort函数我相信大家都不陌生&#xff0c;今天介绍一个新的排序算法stable_sort stable_sort&#xff1a;稳定排序算法&#xff0c;维持相等元素的原有顺序。 stable_sort 假如我们定义一个字符串数组 vector<string> words; //fox jumps over quick red slow the turtl…...

【atoi函数详解】

文章目录 一. 什么是atoi函数二. 模拟实现函数atoi三. 总结atoi 一. 什么是atoi函数 atoi函数是将常量字符串转化为整数 int atoi(const char str);* 注意&#xff1a;该函数的返回值为int&#xff0c;所以不能超过int范围&#xff0c;如果数字很大超过就会截断&#xff0c;返…...

字符串左旋 与 字符串旋转结果

字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 方法1 三步翻转法 要求:abcdef 左旋两个 整体逆序:fedcba左边逆序:cdef ba右边逆序:cdef ab #include<stdio.h> #include<…...

真人现在猫鼠躲猫猫游戏搭建流程:专业思考与深度思考

真人现在猫鼠躲猫猫游戏是种充满乐趣和挑战的团队竞技游戏。本文将从游戏规则设计、场地布置、技术实现和用户体验等方面&#xff0c;深入探讨人现在猫鼠躲猫猫游戏的搭建流程&#xff0c;并结合专业思考与深度思考&#xff0c;为游戏搭建提供全面指导。 一、游戏规则设计&…...

计算机导论实验——Linux基础入门

使用Xshell登录 Linux 主机 linux命令&#xff1a; cd&#xff1a;去哪里 pwd&#xff1a;在哪里 ls&#xff1a;查看当前有什么文件 mkdir&#xff1a;创建新目录 cp&#xff1a;复制 cat&#xff1a;连接或显示文件 rm&#xff1a;删除 mv&#xff1a;用于移动或重命名文件…...

服务运营 |摘要:学术+业界-近期前沿运筹医疗合作精选

推文作者&#xff1a;李舒湉 编者按 本文归纳整理了近期INFORMS Journal on Applied Analytics中的相关业界合作研究。 这些研究成果体现了运筹学在医疗健康领域实践的效果。文中的学术业界合作使用了不同的研究工具。第一篇文章使用仿真模型帮助诊所进行不同拥挤程度下诊所使用…...

基于Dockerfile创建镜像

基于现有镜像创建 1.首先启动一个镜像&#xff0c;在容器里做修改 docker create -it centos:7 /bin/bash #常用选项&#xff1a; -m 说明信息&#xff1b; -a 作者信息&#xff1b; -p 生成过程中停止容器的运行。 2.然后将修改后的容器提交为新的镜像&#xff0c;需要使用…...

架构实战关键知识点

1.维基百科的“系统”定义&#xff1a;https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%B5%B1 2.维基百科的“软件模块”定义&#xff1a;https://zh.wikipedia.org/wiki/%E8%BB%9F%E9%AB%9 4%E6%A8%A1%E7%B5%84 3.维基百科的“软件组件”定义&#xff1a;https://zh.wikipedia.or…...

M1Mac开启x86_64命令行archlinux虚拟机的最佳实践(qemu)

categories: [Tips] tags: Linux MacOS 写在前面 UTM 虚拟机可以卸载了, 命令行才是永远滴神, M1 MacBook Air 又能再战了! 之前一直用 UTM 的虚拟化开启 x86_64 的 Linux 虚拟机的, 但是我发现 UTM 好像不是必须的, 只要有qemu 就可以了, 下面就看看如何不通过图形界面前端…...

深度神经网络压缩与加速技术

// 深度神经网络是深度学习的一种框架&#xff0c;它是一种具备至少一个隐层的神经网络。与浅层神经网络类似&#xff0c;深度神经网络也能够为复杂非线性系统提供建模&#xff0c;但多出的层次为模型提供了更高的抽象层次&#xff0c;因而提高了模型的能力。深度神经网络是一…...

系统架构设计:11 论湖仓一体架构及其应用

目录 一 湖仓一体(Lakehouse) 1 数据仓库 2 数据湖 3 数据仓库和数据湖 4 湖仓一体(Lakehouse)...

Linux系统编程_文件编程第1天:打开、写入、读取、关闭文件等编程

1. 文件编程概述&#xff08;399.1&#xff09; 内容超多&#xff1a; 文件系统原理及访问机制文件在内核中的管理机制什么是文件信息节点inode文件的共享文件权限&#xff0c;各种用户对其权限。。。。。。 应用为王&#xff0c;如&#xff1a; 账单游戏进度配置文件等 关心如…...

scapy构造ND报文

控制报文之&#xff1a;找邻居报文 什么是ND报文 ND报文是指网络中的 Neighbor Discovery&#xff08;ND&#xff09;控制报文。Neighbor Discovery 是 IPv6 网络中的一种协议&#xff0c;它用于管理网络节点之间的邻居关系、地址解析、路由缓存维护和自动配置等任务。ND 协议…...

c++设计模式之单例设计模式

&#x1f482; 个人主页:[pp不会算法v](https://blog.csdn.net/weixin_73548574?spm1011.2415.3001.5343) &#x1f91f; 版权: 本文由【pp不会算法^v^】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦…...

App自动化测试环境搭建

目录 1、java jdk安装 2、node.js安装 3、安装模拟器安装 4、Android SDK 安装 5、Appium-Server安装 6、appium客户端安装  7、Appium-Python-Client安装 只做记录和注意点&#xff0c;详细内容不做解释 环境&#xff1a;winappium夜神模拟器python 需要用到的工具&a…...

win10搭建gtest测试环境+vs2019

首先是下载gtest&#xff0c;这个我已经放在了博客上方资源绑定处&#xff0c;这个适用于win10vs版本&#xff0c;关于liunx版本的不能用这个。 或者百度网盘链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/15m62KAJ29vNe1mrmAcmehA 提取码&#xff1a;vfxz 下…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...