JWT整合springboot 自定义定时更换秘钥

jwt概要:

JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串。

jwt是一种无状态token,可用于oss单点登录

JWT的数据结构以及签发的过程

JWT由三部分构成:header(头部)、payload(载荷)和signature(签名)。

Header 头部信息:指定类型和算法
Payload 荷载信息:存放Claims声明信息
Signature签名:把前两者对应的Json结构进行base64url编码之后的字符串拼接起来和密钥放一起加密后的签名 组成方式为header.payload.signature

Header的结构

  • type 声明类型
  • algorithm 声明加密算法
    格式: {“typ”: “JWT”,“alg”: “HS256”}

payload的结构

  • 声明信息
    payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的声明,这些声明被JWT标准称为claims(声明),它的每个属性键值对其实就是一个claim,JWT常用的有两种声明,一种是Reserved claims(保留声明),也就是JWT规定的标准声明。
    一种是Private claims(自定义声明),我们在这里定义要传递的信息
    还有一种是public claims(公共声明),这个目前没用到。

标准声明(JWT保留声明)

iss(Issuser):代表这个JWT的签发主体;
sub(Subject):代表这个JWT的主体,即它的所有人;
aud(Audience):代表这个JWT的接收对象;
exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
iat(Issuedat):是一个时间戳,代表这个JWT的签发时间; jti(JWT ID):是JWT的唯一标识。

signature

  • 签名
    把header和payload对应的json结构进行base64url编码之后得到的字符串用点号拼接起来,然后根据header里面alg指定的签名算法生成出来的,然后添加自己设定的key进行加密签名

java代码实现:

maven依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version>
</dependency>

定义一个用户信息类,存放一些用户基本信息(如用户名、权限(可进行菜单鉴权))

/*** @author : ljt* @version V1.0* @Description: 用户信息* @date Date : 2021年08月06日 13:56*/
@Data
public class UserLoginBO {/*** 用户id*/private Long id;/*** 用户名*/private String userName;/*** 用户昵称*/private String nickName;/*** 用户真实姓名*/private String realName;/*** 用户openid*/private String openId;/*** 用户token*/private String accessToken;}

JWT工具类:

/*** @author : ljt* @version V1.0* @Description: jwt工具类* @date Date : 2021年08月06日 13:22*/
@Component
public class JwtTokenUtil {/*** jwt生成token秘钥,此处动态更新所以为空,可随便自定义*/public static String TOKEN_SECRET = "";/*** 定义token有效期 秒*/public static Integer tokenExpiration=1800;/*** 生成token* @param subject  用户名* @param claims 用户信息* @return token字符串*/public static String generateToken(String subject, Map<String, Object> claims) {return Jwts.builder().setClaims(claims).setSubject(subject).setId(UUID.randomUUID().toString()).setIssuedAt(new Date()).setExpiration(generateExpirationDate(tokenExpiration)).compressWith(CompressionCodecs.DEFLATE).signWith(SignatureAlgorithm.HS256, TOKEN_SECRET).compact();}/**** 解析token 信息* @param token token字符串* @return 用户map信息*/private static Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;}/*** 生成失效时间* @param expiration 失效时长* @return 到期时间 时间格式*/private static Date generateExpirationDate(long expiration) {return new Date(System.currentTimeMillis() + expiration * 1000);}/*** 根据token 获取用户信息* @param token token* @return 自定义的用户对象*/public static UserLoginBO getUserFromToken(String token) {UserLoginBO userDetail;try {final Claims claims = getClaimsFromToken(token);userDetail = new UserLoginBO();userDetail.setId(Long.parseLong(claims.get("id").toString()));userDetail.setUserName(String.valueOf(claims.get("userName")));userDetail.setNickName(String.valueOf(claims.get("nickName")));userDetail.setRealName(String.valueOf(claims.get("UserName")));userDetail.setOpenId(String.valueOf(claims.get("openId")));} catch (Exception e) {userDetail = null;}return userDetail;}/*** 根据token 获取用户ID* 和获取用户信息一致 此处新定义一个方法是用着方便* @param token token* @return 返回用户id*/private Long getUserIdFromToken(String token) {Long userId;try {final Claims claims = getClaimsFromToken(token);userId = Long.parseLong(claims.get("id").toString());} catch (Exception e) {userId = 0L;}return userId;}/*** 根据token 获取用户名* 和获取用户信息一致 此处新定义一个方法是用着方便* @param token token字符串* @return 用户昵称*/public static String getUsernameFromToken(String token) {String username;try {final Claims claims = getClaimsFromToken(token);username = claims.getSubject();} catch (Exception e) {username = null;}return username;}/*** 刷新token** @param token 原token* @return 新token*/public static String refreshToken(String token,Integer tokenExpiration) {String refreshedToken;try {final Claims claims = getClaimsFromToken(token);refreshedToken = generateToken(claims.getSubject(), claims,tokenExpiration);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}/*** 根据token 获取生成时间* @param token token字符串* @return 时间*/public Date getCreatedDateFromToken(String token) {Date created;try {final Claims claims = getClaimsFromToken(token);created = claims.getIssuedAt();} catch (Exception e) {created = null;}return created;}/*** 根据token 获取过期时间* @param token token* @return 时间*/public static Date getExpirationDateFromToken(String token) {Date expiration;try {final Claims claims = getClaimsFromToken(token);expiration = claims.getExpiration();} catch (Exception e) {expiration = null;}return expiration;}/*** 验证token 是否合法* @param token  token* @param userDetail  用户信息* @return true 或 false*/public boolean validateToken(String token, UserLoginBO userDetail) {final long userId = getUserIdFromToken(token);final String username = getUsernameFromToken(token);return (userId == userDetail.getId()&& username.equals(userDetail.getUserName())&& !isTokenExpired(token));}/*** 判断令牌是否过期* @param token 令牌* @return 是否过期*/public static Boolean isTokenExpired(String token) {final Date expiration = getExpirationDateFromToken(token);return expiration.before(new Date());}}

配置拦截器:

@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {/*** token剩余过期时间* 此处设定剩余到期时间自动刷新token* 根据需要手动编辑刷新token接口*/private final Long TOKEN_DATE = 5*60*1000L;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//token约定放置在请求头中以access_token的方式发送String token = request.getHeader("access_token");log.debug("用户请求鉴权token = {}", token);//判断token是否存在 不存在无权限 直接返回if(StringUtils.isNotEmpty(token))   {//工具类 直接获取用户信息UserLoginBO userFromToken = JwtTokenUtil.getUserFromToken(token);//获取不到 用户授权过期 直接返回if (userFromToken != null && !JwtTokenUtil.isTokenExpired(token)) {//TODO 能正常获取用户信息 可判断用户信息是否一致 一致则鉴权成功 这里我懒省事//判断token剩余过期时间 将过期自动签发新token 以response方式返回if ( JwtTokenUtil.getExpirationDateFromToken(token).getTime() - System.currentTimeMillis() < TOKEN_DATE ){String refreshToken = JwtTokenUtil.refreshToken(token,JwtTokenUtil.tokenExpiration);Object value = CacheUtil.getInstance().getValue(token);CacheUtil.getInstance().putValue(refreshToken,value, CacheConstant.PERMS_INFO);response.setContentType("text/html; charset=UTF-8");Map<String,String> map = new HashMap<>(16);map.put("accessToken",refreshToken);response.setHeader("access_token",refreshToken);}return true;}}response.setContentType("text/html; charset=UTF-8");ResponseBO responseBO = new ResponseBO();responseBO.setCode(ResponseCode.L001.getCode());responseBO.setMsg(ResponseCode.L001.getTitle());response.getWriter().write(JSONObject.toJSONString(responseBO));return false;}
}

注册拦截器,定义拦截规则

@Configuration
public class AuthConfigurer extends WebMvcConfigurationSupport {/*** 拦截器注入*/@Beanpublic LoginInterceptor loginInterceptor() {return new LoginInterceptor();}/*** 添加拦截* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry//注册拦截器 定义拦截接口.addInterceptor(loginInterceptor()).addPathPatterns("/**")// 排除登录接口拦截.excludePathPatterns("/user/login")super.addInterceptors(registry);}/*** 替换Spring默认JSON转换器为fastjson* @param converters*/@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {super.configureMessageConverters(converters);FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);fastConverter.setFastJsonConfig(fastJsonConfig);converters.add(fastConverter);}/*** 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源** @param registry*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//将所有/static/** 访问都映射到classpath:/static/ 目录下registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");}/*** 配置servlet处理*/@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}
}

生成JWT秘钥工具类:

/*** @author : ljt* @version V1.0* @Description: 生成JWT秘钥* @date Date : 2021年11月25日 15:34*/
@Slf4j
public class CreateJWTKeyUtil {//生成60位秘钥串public static String createJwtKey(){String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+<>?:,|./;'";Random random=new Random();StringBuilder sb=new StringBuilder();for(int i=0;i<60;i++){int number=random.nextInt(84);sb.append(str.charAt(number));}log.info("key====" + sb.toString());return sb.toString();}
}

定时修改JWT秘钥:

/*** @author : ljt* @version V1.0* @Description: 定时修改jwtkey* @date Date : 2021年11月25日 15:43*/
@Component
public class CreateJWTKey {/*** jwt秘钥存储,可选择mysql、redis*/@Autowiredprivate IJwtKeyService jwtKeyService;/*** 可自定义更换周期* 注意:秘钥更换后,已签发token将无法解密,返回结果为token失效,需要重新签发*/@Scheduled(cron = "0 0 0 1/7 * ?")public void createJtw(){//获取新秘钥String jwtKey = CreateJWTKeyUtil.createJwtKey();//秘钥存储修改jwtKeyService.updateJwtKey(jwtKey);//秘钥静态变量修改 可减少实现层查询次数 直接使用静态数据JwtTokenUtil.TOKEN_SECRET = jwtKey;}
}

项目启动时获取秘钥:

@Component
public class ApplicationRunnerImplConfig implements ApplicationRunner {@Autowiredprivate IJwtKeyService jwtKeyService;/*** 项目启动获取jwt秘钥* @param args* @throws Exception*/@Overridepublic void run(ApplicationArguments args) throws Exception {//从存储库中获取秘钥JwtTokenUtil.TOKEN_SECRET = jwtKeyService.findJwtKey();}
}

使用示例:登录成功后返回token

Map<String, Object> map = new HashMap<>(16);
map.put("id",login.getId());
map.put("userName",login.getUserName());
map.put("nickName",login.getNickName());
map.put("realName",login.getRealName());
String token = JwtTokenUtil.generateToken("user", map,JwtTokenUtil.userTokenExpiration);
login.setAccessToken(token);return login;

总结:
使用流程就是:
前端发起登录请求->后端签发token->前端每次请求都携带此token->后端经过拦截器校验->后端逻辑处理->返回前端

JWT整合springboot 自定义定时更换秘钥相关推荐

  1. SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)

    学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识  1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...

  2. 【SpringBoot】2021终极版shiro+jwt整合策略,包含shiro1.5+新特性,极简配置,全网独家。

    2021终极版shiro+jwt整合策略,包含shiro1.5+新特性,极简配置,全网独家. 前言:shiro1.4的配置之繁琐业内闻名,其实它自1.5之后就有了不小的改进,能够大大精简我们前期的整合 ...

  3. JWT认证原理、整合springboot实战应用

    JWT认证原理.整合springboot实战应用 1.什么是JWT 2.JWT能做什么 3.与传统的session认证做对比 4.JWT结构 5.JWT的封装方法 1.什么是JWT JWT(Json ...

  4. JWT 教程_1 SpringBoot与JWT整合

    https://gitee.com/fakerlove/spring-security 文章目录 1. 介绍 2. SpringBoot与JWT整合 2.1 JWT的结构: 2.2 JWT的使用测试: ...

  5. 【Java编程系列】JWT秘钥生成

    热门系列: [算法系列]实战篇:Diffie-Hellman算法实现通信秘钥流程 目录 1.JWT简介 2.JWT的优缺点 3.JWT组成部分 4.JWT的使用 4.1 生成公钥私钥命令 4.2 JW ...

  6. Redis整合SpringBoot,出现“\xac\xed\x00\x05t\x00\x03解决自定义RedisTemplate序列化

    SpringBoot整合 SpringBoot操作数据:spring-data jpa jdbc mongodb redis SpringData也是和SpringBoot齐名的项目 说明:在Spri ...

  7. Python桌面自定义---实现定时更换桌面壁纸

    Python桌面自定义---实现定时更换桌面壁纸 1 效果 2 获取大量壁纸 3 Python代码实现定时更换壁纸 1 效果   大致效果如下,设置过定时更换壁纸的应该都知道是啥场景. 2 获取大量壁 ...

  8. jwt Claims token 秘钥稍有不同也能解析成功 signWith setSigningKey

    问题描述 使用io.jsonwebtoken.Jwts构造了一个token,在解析这个token时,发现解析秘钥和构建秘钥不完全相同也可以成功解析,代码如下 签发token /** 测试生成token ...

  9. rabbitmq消息队列入门到整合springboot(篇幅较长内容详细)

    1.安装rabbitmq服务器 我们选择在linux下安装 安装的前提需要在虚拟机下安装docker docker pull rabbitmq:management(拉去镜像) docker run ...

最新文章

  1. 如何看待 2020 届校招算法岗「爆炸」的情况?
  2. 关系数据库的数据迁移
  3. itertools库
  4. webview检测html事件,在JavaFX WebView中检测HTML textarea onkeyup事件
  5. 离散数学及其应用 (Kenneth H·Rosen 著)
  6. 81-linux cluster : lvs
  7. Jeecg-Boot 快速生成前后端代码
  8. 【树莓派】iptables相关配置
  9. movingpandas时空数据分析——旧金山出租车轨迹数据集处理
  10. 广播前置放大器的作用_IP网络广播前置放大器
  11. cad画正弦曲线lisp_cadlisp基础教程.pdf
  12. 小程序设置page背景图片透明度
  13. 个人主页网页设计模板
  14. JS学习之路系列总结四象阵(此文犹如武林之中的易筋经,是你驰骋IT界的武功心法,学会JS五大阵法就学会了JS,博主建议先学三才阵)...
  15. flutter Icon一览表,使用字符映射表查看所有图标并使用
  16. IIS管理器无法打开。启动后,在任务栏中有,但是窗口不见了,另一种解决办法.
  17. 史上最全的CTF保姆教程 从入门到入狱【带工具】
  18. uip-udp-demo分析---基于contiki
  19. cisco 交换机 定期 自动 备份配置 -linux,Cisco交换机配置文件定时自动备份
  20. 关于Google收购FeedBurner的一点感想

热门文章

  1. abort()函数使用
  2. A5M2数据库工具下载
  3. 计算机耐火等级不低于,网络信息安全知识:网吧营业场所应设置在耐火等级不低于()级的建筑物内。...
  4. 推荐一本网络科学入门书
  5. PostgreSQL恢复误删数据
  6. JS随机漂浮广告代码
  7. goahead移植和使用
  8. 企业微信如何请假?如何撤销申请?
  9. 【Linux操作系统】1. Linux操作系统简介、安装
  10. 遥控小车(基于TCP/IP)