当前位置:   article > 正文

SpringBoot 集成token实践详解_springboot jwt 双token

springboot jwt 双token

  token是服务端生成的一串字符串,以作客户端进行请求的令牌,当第一次登陆后,服务器生成一个token便将此token返回给客户端,以后客户端只要带上这个token前来请求数据即可,无需再次带上用户名和密码

一、需求

  1. SpringBoot 集成 JWT(token)

  2. 拦截器自动验证验证 token 是否过期

  3. token 自动刷新(单个 token 刷新机制,保证活跃用户不会掉线)

  4. 标准统一的 RESTFul 返回体数据格式

  5. 异常统一拦截处理

单个 token 刷新机制(介绍):

token 距离发布token 2 个小时内的token为新生token,2-3 个小时的token为老年token

每次请求,前端带上 token,

(1)如果 token 为新 token ,服务器返回原来的 token

(2)如果 token 为老年 token,服务器返回 刷新后的新生token ,

(3)如果 token 为过期 token,服务器返回token过期 状态码 401,,请求失败, 前端重新登录

二、代码
1. 导入依赖
jwt 依赖

  1. <dependency>
  2.     <groupId>com.auth0</groupId>
  3.     <artifactId>java-jwt</artifactId>
  4.     <version>3.10.3</version>
  5. </dependency>

2. 配置文件

  1. server:
  2. port: 8081
  3. spring:
  4. application:
  5. name: tokendemo
  6. # token
  7. token:
  8. privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
  9. yangToken: 1000000
  10. oldToken: 3000000000

3. 代码

代码结构如下

  1. AuthWebMvcConfigurer

  1. @Configuration
  2. public class AuthWebMvcConfigurer implements WebMvcConfigurer {
  3. @Autowired
  4. AuthHandlerInterceptor authHandlerInterceptor;
  5. /**
  6. * 给除了 /login 的接口都配置拦截器,拦截转向到 authHandlerInterceptor
  7. */
  8. @Override
  9. public void addInterceptors(InterceptorRegistry registry) {
  10. registry.addInterceptor(authHandlerInterceptor)
  11. .addPathPatterns("/**")
  12. .excludePathPatterns("/login");
  13. }
  14. }
  1. TokenTestController
  1. @RestController
  2. public class TokenTestController {
  3. @Autowired
  4. TokenUtil tokenUtil;
  5. /**
  6. * 使用 /login 请求获得 token, /login 不经过拦截器
  7. */
  8. @RequestMapping("/login")
  9. public String login(){
  10. return tokenUtil.getToken("靓仔","admin");
  11. }
  12. /**
  13. * 使用 /test-token 测试 token,进过拦截器
  14. */
  15. @RequestMapping("/test-token")
  16. public Map testToken(HttpServletRequest request){
  17. String token = request.getHeader("token");
  18. return tokenUtil.parseToken(token);
  19. }
  20. }
  1. TokenAuthExpiredException
  1. public class TokenAuthExpiredException extends RuntimeException{
  2. }
  1. AuthHandlerInterceptor
  1. @Slf4j
  2. @Component
  3. public class AuthHandlerInterceptor implements HandlerInterceptor {
  4. @Autowired
  5. TokenUtil tokenUtil;
  6. @Value("${token.privateKey}")
  7. private String privateKey;
  8. @Value("${token.yangToken}")
  9. private Long yangToken;
  10. @Value("${token.oldToken}")
  11. private Long oldToken;
  12. /**
  13. * 权限认证的拦截操作.
  14. */
  15. @Override
  16. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
  17. log.info("=======进入拦截器========");
  18. // 如果不是映射到方法直接通过,可以访问资源.
  19. if (!(object instanceof HandlerMethod)) {
  20. return true;
  21. }
  22. //为空就返回错误
  23. String token = httpServletRequest.getHeader("token");
  24. if (null == token || "".equals(token.trim())) {
  25. return false;
  26. }
  27. log.info("==============token:" + token);
  28. Map<String, String> map = tokenUtil.parseToken(token);
  29. String userId = map.get("userId");
  30. String userRole = map.get("userRole");
  31. long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));
  32. //1.判断 token 是否过期
  33. //年轻 token
  34. if (timeOfUse < yangToken) {
  35. log.info("年轻 token");
  36. }
  37. //老年 token 就刷新 token
  38. else if (timeOfUse >= yangToken && timeOfUse < oldToken) {
  39. httpServletResponse.setHeader("token",tokenUtil.getToken(userId,userRole));
  40. }
  41. //过期 token 就返回 token 无效.
  42. else {
  43. throw new TokenAuthExpiredException();
  44. }
  45. //2.角色匹配.
  46. if ("user".equals(userRole)) {
  47. log.info("========user账户============");
  48. return true;
  49. }
  50. if ("admin".equals(userRole)) {
  51. log.info("========admin账户============");
  52. return true;
  53. }
  54. return false;
  55. }
  56. }
  1. GlobalExceptionHandler
  1. @Slf4j
  2. @ControllerAdvice
  3. public class GlobalExceptionHandler {
  4. /**
  5. * 用户 token 过期
  6. * @return
  7. */
  8. @ExceptionHandler(value = TokenAuthExpiredException.class)
  9. @ResponseBody
  10. public String tokenExpiredExceptionHandler(){
  11. log.warn("用户 token 过期");
  12. return "用户 token 过期";
  13. }
  14. }
  1. TokenUtil
  1. @Component
  2. public class TokenUtil {
  3. @Value("${token.privateKey}")
  4. private String privateKey;
  5. /**
  6. * 加密token.
  7. */
  8. public String getToken(String userId, String userRole) {
  9. //这个是放到负载payLoad 里面,魔法值可以使用常量类进行封装.
  10. String token = JWT
  11. .create()
  12. .withClaim("userId" ,userId)
  13. .withClaim("userRole", userRole)
  14. .withClaim("timeStamp", System.currentTimeMillis())
  15. .sign(Algorithm.HMAC256(privateKey));
  16. return token;
  17. }
  18. /**
  19. * 解析token.
  20. * (优化可以用常量固定魔法值+使用DTO在 mvc 之前传输数据,而不是 map,这里因为篇幅原因就不做了)
  21. * {
  22. * "userId": "3412435312",
  23. * "userRole": "ROLE_USER",
  24. * "timeStamp": "134143214"
  25. * }
  26. */
  27. public Map<String, String> parseToken(String token) {
  28. HashMap<String, String> map = new HashMap<>();
  29. DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(privateKey))
  30. .build().verify(token);
  31. Claim userId = decodedjwt.getClaim("userId");
  32. Claim userRole = decodedjwt.getClaim("userRole");
  33. Claim timeStamp = decodedjwt.getClaim("timeStamp");
  34. map.put("userId", userId.asString());
  35. map.put("userRole", userRole.asString());
  36. map.put("timeStamp", timeStamp.asLong().toString());
  37. return map;
  38. }
  39. }

三、测试



1. 获得 token

访问

localhost:8081/login

效果:

image-20210211102129000

2. 测试 token 是否可用

将 1 测试得到的 token 放到 header 里面测试 token是否可用

访问

localhost:8081/test-token

image-20210211102448340

3. 测试 token 过期

测试全局异常拦截类拦截到 TokenAuthExpiredException 异常,然后返回提示。

将过期时间调小,修改 application.yaml 文件,3 秒钟就过期

  1. server:
  2.     port: 8081
  3. spring:
  4.     application:
  5.         name: tokendemo
  6. # token
  7. token:
  8.     privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
  9.     yangToken: 1000
  10.     oldToken: 3000

重启应用测试:image-20210211102718111

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/368043
推荐阅读
相关标签
  

闽ICP备14008679号