• Springboot 整合 JWT + Redis 实现双Token 校验Demo(简单实现)


    一、新建一个SpringBoot 项目,springboot项目创建过程详见 

    mac idea 创建 springboot 项目_JAVA·D·WangJing的博客-CSDN博客_mac idea创建springboot项目

    二、SpringBoot 整合使用 Rdis

    SpringBoot 项目 添加 redis配置_JAVA·D·WangJing的博客-CSDN博客_springboot添加redis

    三、SpringBoot 整合 JWT

    3.1、pom.xml依赖配置

    1. <!-- JWT依赖 -->
    2. <dependency>
    3. <groupId>io.jsonwebtoken</groupId>
    4. <artifactId>jjwt</artifactId>
    5. <version>0.7.0</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>com.auth0</groupId>
    9. <artifactId>java-jwt</artifactId>
    10. <version>3.4.0</version>
    11. </dependency>
    12. <!-- JSON 解析器和生成器 -->
    13. <dependency>
    14. <groupId>com.alibaba</groupId>
    15. <artifactId>fastjson</artifactId>
    16. <version>1.2.83</version>
    17. </dependency>

    3.2、application.yml 增加配置

    1. # 应用服务 WEB 访问端口
    2. server:
    3. port: 8080
    4. spring:
    5. # 应用名称
    6. application:
    7. name: jwt-demo
    8. # redis配置
    9. redis:
    10. # 地址
    11. host: 127.0.0.1
    12. # 端口,默认为6379
    13. port: 6379
    14. # 连接超时时间
    15. timeout: 10s
    16. # 密码
    17. password: wangjing
    18. # JWT 配置
    19. jwt:
    20. # 加密密钥
    21. secret: wangjing
    22. # header 名称
    23. header: Authorization
    24. # token有效时长 S
    25. expire:
    26. accessToken: 3600
    27. refreshToken: 4000

    3.3、JwtToken 工具类

    1. package com.wangjing.jwtdemo.util;
    2. import com.alibaba.fastjson.JSONObject;
    3. import com.wangjing.jwtdemo.constants.Constants;
    4. import com.wangjing.jwtdemo.vo.UserToken;
    5. import com.wangjing.jwtdemo.vo.UserTokenInfo;
    6. import io.jsonwebtoken.Claims;
    7. import io.jsonwebtoken.Jwts;
    8. import io.jsonwebtoken.SignatureAlgorithm;
    9. import org.springframework.beans.BeanUtils;
    10. import org.springframework.beans.factory.annotation.Autowired;
    11. import org.springframework.beans.factory.annotation.Value;
    12. import org.springframework.stereotype.Component;
    13. import java.util.Date;
    14. /**
    15. * @author: wangjing
    16. * @createTime: 2022-11-23 14:55
    17. * @version: 1.0.0
    18. * @Description: JwtToken 工具类
    19. */
    20. @Component
    21. public class JwtTokenUtil {
    22. @Value("${jwt.secret}")
    23. public String secret;
    24. @Value("${jwt.header}")
    25. public String header;
    26. @Value("${jwt.expire.accessToken}")
    27. public Integer accessTokenExpire;
    28. @Value("${jwt.expire.refreshToken}")
    29. public Integer refreshTokenExpire;
    30. @Autowired
    31. RedisUtil redisUtil;
    32. /**
    33. * 创建 刷新令牌 与 访问令牌 关联关系
    34. *
    35. * @param userToken
    36. * @param refreshTokenExpireDate
    37. */
    38. public void tokenAssociation(UserToken userToken, Date refreshTokenExpireDate) {
    39. Long time = (refreshTokenExpireDate.getTime() - System.currentTimeMillis()) / 1000 + 100;
    40. redisUtil.set(userToken.getRefreshToken(), userToken.getAccessToken(), time);
    41. }
    42. /**
    43. * 根据 刷新令牌 获取 访问令牌
    44. *
    45. * @param refreshToken
    46. */
    47. public String getAccessTokenByRefresh(String refreshToken) {
    48. Object value = redisUtil.get(refreshToken);
    49. return value == null ? null : String.valueOf(value);
    50. }
    51. /**
    52. * 添加至黑名单
    53. *
    54. * @param token
    55. * @param expireTime
    56. */
    57. public void addBlacklist(String token, Date expireTime) {
    58. Long expireTimeLong = (expireTime.getTime() - System.currentTimeMillis()) / 1000 + 100;
    59. redisUtil.set(getBlacklistPrefix(token), "1", expireTimeLong);
    60. }
    61. /**
    62. * 校验是否存在黑名单
    63. *
    64. * @param token
    65. * @return true 存在 false不存在
    66. */
    67. public Boolean checkBlacklist(String token) {
    68. return redisUtil.hasKey(getBlacklistPrefix(token));
    69. }
    70. /**
    71. * 获取黑名单前缀
    72. *
    73. * @param token
    74. * @return
    75. */
    76. public String getBlacklistPrefix(String token) {
    77. return Constants.TOKEN_BLACKLIST_PREFIX + token;
    78. }
    79. /**
    80. * 获取 token 信息
    81. *
    82. * @param userTokenInfo
    83. * @return
    84. */
    85. public UserToken createToekns(UserTokenInfo userTokenInfo) {
    86. Date nowDate = new Date();
    87. Date accessTokenExpireDate = new Date(nowDate.getTime() + accessTokenExpire * 1000);
    88. Date refreshTokenExpireDate = new Date(nowDate.getTime() + refreshTokenExpire * 1000);
    89. UserToken userToken = new UserToken();
    90. BeanUtils.copyProperties(userTokenInfo, userToken);
    91. userToken.setAccessToken(createToken(userTokenInfo, nowDate, accessTokenExpireDate));
    92. userToken.setRefreshToken(createToken(userTokenInfo, nowDate, refreshTokenExpireDate));
    93. // 创建 刷新令牌 与 访问令牌 关联关系
    94. tokenAssociation(userToken, refreshTokenExpireDate);
    95. return userToken;
    96. }
    97. /**
    98. * 生成token
    99. *
    100. * @param userTokenInfo
    101. * @return
    102. */
    103. public String createToken(UserTokenInfo userTokenInfo, Date nowDate, Date expireDate) {
    104. return Jwts.builder()
    105. .setHeaderParam("typ", "JWT")
    106. .setSubject(JSONObject.toJSONString(userTokenInfo))
    107. .setIssuedAt(nowDate)
    108. .setExpiration(expireDate)
    109. .signWith(SignatureAlgorithm.HS512, secret)
    110. .compact();
    111. }
    112. /**
    113. * 获取 token 中注册信息
    114. *
    115. * @param token
    116. * @return
    117. */
    118. public Claims getTokenClaim(String token) {
    119. try {
    120. return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    121. } catch (Exception e) {
    122. return null;
    123. }
    124. }
    125. /**
    126. * 验证 token 是否过期失效
    127. *
    128. * @param token
    129. * @return true 过期 false 未过期
    130. */
    131. public Boolean isTokenExpired(String token) {
    132. return getExpirationDate(token).before(new Date());
    133. }
    134. /**
    135. * 获取 token 失效时间
    136. *
    137. * @param token
    138. * @return
    139. */
    140. public Date getExpirationDate(String token) {
    141. return getTokenClaim(token).getExpiration();
    142. }
    143. /**
    144. * 获取 token 发布时间
    145. *
    146. * @param token
    147. * @return
    148. */
    149. public Date getIssuedAtDate(String token) {
    150. return getTokenClaim(token).getIssuedAt();
    151. }
    152. /**
    153. * 获取用户信息
    154. *
    155. * @param token
    156. * @return
    157. */
    158. public UserTokenInfo getUserInfoToken(String token) {
    159. String subject = getTokenClaim(token).getSubject();
    160. UserTokenInfo userTokenInfo = JSONObject.parseObject(subject, UserTokenInfo.class);
    161. return userTokenInfo;
    162. }
    163. /**
    164. * 获取用户名
    165. *
    166. * @param token
    167. * @return
    168. */
    169. public String getUserName(String token) {
    170. UserTokenInfo userInfoToken = getUserInfoToken(token);
    171. return userInfoToken.getUserName();
    172. }
    173. /**
    174. * 获取用户Id
    175. *
    176. * @param token
    177. * @return
    178. */
    179. public Long getUserId(String token) {
    180. UserTokenInfo userInfoToken = getUserInfoToken(token);
    181. return userInfoToken.getUserId();
    182. }
    183. }

    3.4、JwtFilter 拦截器

    1. package com.wangjing.jwtdemo.filter;
    2. import com.wangjing.jwtdemo.util.JwtTokenUtil;
    3. import com.wangjing.jwtdemo.vo.UserTokenInfo;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.stereotype.Component;
    6. import org.springframework.util.StringUtils;
    7. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    8. import javax.annotation.Resource;
    9. import javax.servlet.http.HttpServletRequest;
    10. import javax.servlet.http.HttpServletResponse;
    11. /**
    12. * @author: wangjing
    13. * @createTime: 2022-10-17 19:03
    14. * @version: 1.0.0
    15. * @Description: Jwt 拦截器
    16. */
    17. @Slf4j
    18. @Component
    19. public class JwtFilter extends HandlerInterceptorAdapter {
    20. @Resource
    21. JwtTokenUtil jwtTokenUtil;
    22. @Override
    23. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    24. // 获取token
    25. String token = request.getHeader(jwtTokenUtil.header);
    26. if (StringUtils.isEmpty(token)) {
    27. token = request.getParameter(jwtTokenUtil.header);
    28. }
    29. if (StringUtils.isEmpty(token)) {
    30. // 只是简单DEMO,这里直接返回false,可以自己进行添加
    31. log.error("token 不能为空!");
    32. return false;
    33. }
    34. // 判断token是否超时
    35. if (jwtTokenUtil.isTokenExpired(token)) {
    36. log.error("token 已失效!");
    37. return false;
    38. }
    39. // 判断 token 是否已在黑名单
    40. if (jwtTokenUtil.checkBlacklist(token)) {
    41. log.error("token 已被加入黑名单!");
    42. return false;
    43. }
    44. // 获取用户信息
    45. UserTokenInfo userInfoToken = jwtTokenUtil.getUserInfoToken(token);
    46. // 通过用户信息去判断用户状态,等业务
    47. //TODO 涉及到业务,这里不在阐述
    48. return true;
    49. }
    50. }

    3.5、WebConfig 类(注入拦截器)

    1. package com.wangjing.jwtdemo.config;
    2. import com.wangjing.jwtdemo.filter.JwtFilter;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    6. import javax.annotation.Resource;
    7. /**
    8. * @author: wangjing
    9. * @createTime: 2022-10-17 19:02
    10. * @version: 1.0.0
    11. * @Description: 请求拦截器
    12. */
    13. @Configuration
    14. public class WebConfig implements WebMvcConfigurer {
    15. @Resource
    16. private JwtFilter jwtFilter;
    17. /**
    18. * 不需要拦截地址
    19. */
    20. public static final String[] EXCLUDE_URLS = {
    21. "/login/**"
    22. };
    23. @Override
    24. public void addInterceptors(InterceptorRegistry registry) {
    25. registry.addInterceptor(jwtFilter).addPathPatterns("/**")
    26. .excludePathPatterns(EXCLUDE_URLS);
    27. }
    28. }

    3.6、LoginController(简单的代码实现)

    1. package com.wangjing.jwtdemo.controller;
    2. import com.wangjing.jwtdemo.entity.Result;
    3. import com.wangjing.jwtdemo.enums.ResultTypeEnum;
    4. import com.wangjing.jwtdemo.util.JwtTokenUtil;
    5. import com.wangjing.jwtdemo.vo.LoginBody;
    6. import com.wangjing.jwtdemo.vo.UserToken;
    7. import com.wangjing.jwtdemo.vo.UserTokenInfo;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.util.StringUtils;
    10. import org.springframework.web.bind.annotation.*;
    11. /**
    12. * @author wangjing
    13. * @since 2022-10-18
    14. */
    15. @RestController
    16. @RequestMapping("/login")
    17. public class LoginController {
    18. @Autowired
    19. JwtTokenUtil jwtTokenUtil;
    20. /**
    21. * 登录
    22. *
    23. * @param loginBody
    24. * @return
    25. */
    26. @PostMapping("/login")
    27. public Result<UserToken> login(@RequestBody LoginBody loginBody) {
    28. // 业务验证:入参校验 + 用户信息校验查询
    29. //TODO 涉及到业务,这里不在阐述
    30. UserTokenInfo userTokenInfo = new UserTokenInfo();
    31. userTokenInfo.setUserId(1L);
    32. userTokenInfo.setUserName("wangjing");
    33. userTokenInfo.setRealName("王京");
    34. // 生成Token
    35. UserToken userToken = jwtTokenUtil.createToekns(userTokenInfo);
    36. return new Result<>(ResultTypeEnum.SUCCESS, userToken);
    37. }
    38. /**
    39. * 刷新令牌
    40. *
    41. * @param refreshToken
    42. * @return
    43. */
    44. @PostMapping("/refreshToken/{refreshToken}")
    45. public Result<UserToken> refreshToken(@PathVariable("refreshToken") String refreshToken) {
    46. // 判断token是否超时
    47. if (jwtTokenUtil.isTokenExpired(refreshToken)) {
    48. return new Result<>(ResultTypeEnum.TOKEN_INVALID);
    49. }
    50. // 刷新令牌 放入黑名单
    51. jwtTokenUtil.addBlacklist(refreshToken, jwtTokenUtil.getExpirationDate(refreshToken));
    52. // 访问令牌 放入黑名单
    53. String odlAccessToken = jwtTokenUtil.getAccessTokenByRefresh(refreshToken);
    54. if (!StringUtils.isEmpty(odlAccessToken)) {
    55. jwtTokenUtil.addBlacklist(odlAccessToken, jwtTokenUtil.getExpirationDate(odlAccessToken));
    56. }
    57. // 生成新的 访问令牌 和 刷新令牌
    58. UserTokenInfo userInfoToken = jwtTokenUtil.getUserInfoToken(refreshToken);
    59. // 生成Token
    60. UserToken userToken = jwtTokenUtil.createToekns(userInfoToken);
    61. return new Result<>(ResultTypeEnum.TOKEN_INVALID, userToken);
    62. }
    63. /**
    64. * 登出
    65. *
    66. * @return
    67. */
    68. @PostMapping("/logOut/{token}")
    69. public Result logOut(@PathVariable("token") String token) {
    70. // 放入黑名单
    71. jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));
    72. return new Result<>(ResultTypeEnum.SUCCESS);
    73. }
    74. /**
    75. * 注销
    76. *
    77. * @return
    78. */
    79. @PostMapping("/logOff/{token}")
    80. public Result logOff(@PathVariable("token") String token) {
    81. // 修改用户状态
    82. //TODO 涉及到业务,这里不在阐述
    83. // 放入黑名单
    84. jwtTokenUtil.addBlacklist(token, jwtTokenUtil.getExpirationDate(token));
    85. return new Result<>(ResultTypeEnum.SUCCESS);
    86. }
    87. }

     3.7、本文只列出相对重要的类,具体Demo 下载地址:

    https://download.csdn.net/download/wang_jing_jing/87127439

    四、通过postman 简单测试

    注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!

  • 相关阅读:
    (附源码)mysql+ssm医院挂号系统 毕业设计 250858
    1、Flutter移动端App实战教程【环境配置、模拟器配置】
    在ensp上配置真机实验 待更新~
    flink 流的合并
    spring cloud 微服务
    [网络工程师]-防火墙-防火墙技术
    USB 转串口芯片 CH340
    一文带你了解线程池原理
    C/C++网络编程基础知识超详细讲解上部分(系统性学习day11)
    【JAVA基础】ReentrantLock 加锁和释放锁流程源码级深度解析(包括公平锁和非公平锁实现)
  • 原文地址:https://blog.csdn.net/wang_jing_jing/article/details/128001792