当前位置: 首页 > 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;}}
http://www.lryc.cn/news/191524.html

相关文章:

  • asp.net会议预约管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
  • 十六、【橡皮擦工具组】
  • BAT学习——计算当前路径下指定格式文件的MD5值,将文件名与MD5值写入文本
  • 排序算法(stable_sort(), sort())
  • 【atoi函数详解】
  • 字符串左旋 与 字符串旋转结果
  • 真人现在猫鼠躲猫猫游戏搭建流程:专业思考与深度思考
  • 计算机导论实验——Linux基础入门
  • 服务运营 |摘要:学术+业界-近期前沿运筹医疗合作精选
  • 基于Dockerfile创建镜像
  • 架构实战关键知识点
  • M1Mac开启x86_64命令行archlinux虚拟机的最佳实践(qemu)
  • 深度神经网络压缩与加速技术
  • 系统架构设计:11 论湖仓一体架构及其应用
  • Linux系统编程_文件编程第1天:打开、写入、读取、关闭文件等编程
  • scapy构造ND报文
  • c++设计模式之单例设计模式
  • App自动化测试环境搭建
  • win10搭建gtest测试环境+vs2019
  • 【代码随想录】算法训练营 第二天 第一章 数组 Part 2
  • 在深度学习中,累计不同批次的损失估计总体损失
  • 论文导读|八月下旬Operations Research文章精选:定价问题专题
  • (三)Apache log4net™ 手册 -演示
  • VScode远程root权限调试
  • 【ARM CoreLink 系列 7 -- TZC-400控制器简介】
  • 【C++】-c++11的知识点(中)--lambda表达式,可变模板参数以及包装类(bind绑定)
  • 浅析倾斜摄影三维模型(3D)几何坐标精度偏差的几个因素
  • 【广州华锐互动】智轨列车AR互动教学系统
  • 驾驶数字未来:汽车业界数字孪生技术的崭新前景
  • JVM 性能调优参数