• jwt 保证前端刷新不掉线


    一、简介

    JWT 本身是一个字符串,可以保存一些信息并设置过期时间,下面将用户名加密生成 token 字符串,token 用来校验登录状态是否过期,通常使用 Redis + jwt 实现刷新不掉线,小项目可以不使用 Redis

    刷新不掉线原理,数据格式如下:以 token-1 为例说明

    • 数据格式如下,设置两层数据过期时间
    • 前端 header 请求头每次传递的 token 为 token-1 的 key,前端 header 请求头中 token 不需要改变
    • jwt1 过期时间为 15 分钟,token-1 过期时间为 30 分钟
    • 在拦截器判断如果 jwt1 过期,但 token-1 未过期,则重新生成 token-1 的 value
    • 写一个定时任务,定时判断 token-1 是否过期
    {
    	"token-1": {
    		"value": "jwt1",
    		"createTime": 2022 - 11 - 09 17: 30: 01,
    		"saveTime": 30 * 60
    	},
    	"token-2": {
    		"value": "jwt2",
    		"createTime": 2022 - 11 - 09 18: 30: 01,
    		"saveTime": 30 * 60
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    JWT 详细使用可参考 https://yixiu.blog.csdn.net/article/details/118409124

    springboot 项目简单示例如下

    二、新增 token 实体类
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class TokenEntity {
    
        // 保存 jwt 加密的字符串
        private String value;
    
        // 创建时间
        private Date createTime;
    
        // 保存时间,单位 s
        private int saveTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    三、新增 jwt 工具类

    jwt 工具类包含,创建 token、校验 token、从 token 获取用户名

    @Component
    @Slf4j
    public class TokenUtil {
    	
    	/* 保存 token */
        public Map<String, TokenEntity> map = new HashMap<>();
    
        /* jwt 过期时间 :30*60 s */
        @Value("${jwt.expireTime}")
        public int expireTime;
    
        /* jwt 加密密码 : 123456 */
        @Value("${jwt.password}")
        public String password;
        
        @Autowired
        UserService userService;
    
        /**
         * Description: 根据 userName 生成 token
         */
    
        public String createToken(String userName) {
            return JWT.create().withHeader(new HashMap<>())
                    .withClaim("userName", userName)
                    .withExpiresAt(Instant.now().plusSeconds(expireTime / 2)) // 设置过期时间
                    .sign(Algorithm.HMAC512(password));
        }
    
        /**
         * Description: 从 token 中获取用户名
         */
    
        public String getUserName(String token) {
            try {
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("userName").asString();
            } catch (Exception e) {
                return null;
            }
        }
    
        /**
         * Description: 校验 token 有效性
         */
    
        public boolean checkToken(String token) {
            try {
                // 根据密码生成JWT效验器
                Algorithm algorithm = Algorithm.HMAC512(password);
                JWTVerifier verifier = JWT.require(algorithm).build();
                // 效验TOKEN
                String userName = verifier.verify(token).getClaim("userName").asString();
                if (StringUtils.isEmpty(userName)) return false;
                // 查询 userName 是否存在
                User user = userService.getUserName(userName); 
                return null!=user ;
            } catch (Exception e) {
                log.error("JWT 校验用户token失败");
                return false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    四、拦截器中校验 token
    @Configuration
    public class MyWebMvcConfig implements WebMvcConfigurer {
        @Autowired
        LoginInterceptor loginInterceptor;
    
        /**
         * Description: 拦截接口
         */
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(loginInterceptor).addPathPatterns("/**")// 拦截所有请求,包括静态资源
                    .excludePathPatterns("/user/login"); // 放行登录接口
        }
    }
    
    @Slf4j
    @Component
    class LoginInterceptor implements HandlerInterceptor {
    
        @Autowired
        TokenUtil tokenUtil;
    
        /**
         * Description: 检验 token
         */
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Headers", "*");
            String token = request.getHeader("TOKEN");
            // 判断前端 token 不为空,并且保存 token 的 map 对象中存在该 key
            if (StringUtils.isNotEmpty(token) && null != tokenUtil.map.get(token)) {
            	// 校验 jwt 是否过期
                if (!tokenUtil.checkToken(tokenUtil.map.get(token).getValue())) {
                    String userName = tokenUtil.getUserName(token);
                    if (StringUtils.isNotEmpty(userName)) {
                        String newToken = tokenUtil.createToken(userName);
                       // System.out.println("newToken--------------->" + newToken);
                        TokenEntity tokenEntity = new TokenEntity(newToken, new Date(), tokenUtil.expireTime);
                        tokenUtil.map.put(token, tokenEntity);
                    }
                }
                return true;
            }
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(JSONObject.toJSONString(new JSONObject().put("error","TOKEN 无效")));
            log.error("TOKEN 无效");
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    五、定时任务删除 token
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(CadreReserveApplication.class);
            ConfigurableApplicationContext applicationContext = springApplication.run(args);
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    TokenUtil tokenUtil = applicationContext.getBean(TokenUtil.class);
                    Map<String, TokenEntity> map = tokenUtil.map;
                    for (Map.Entry<String, TokenEntity> entry : map.entrySet()) {
                        TokenEntity tokenEntity = entry.getValue();
                        long nowTime = System.currentTimeMillis();
                        long createTime = tokenEntity.getCreateTime().getTime();
                        // 判断 token 是否过期,创建时间 + 保存时间小于当前时间则删除
                        if (nowTime > createTime + tokenEntity.getSaveTime() * 1000L) {
                            map.remove(entry.getKey());
                            log.info("-------------- 删除 token 成功 --------------");
                        }
                    }
                }
            };
            Timer timer = new Timer();
            // 启动延迟 3 s 执行,执行周期间隔 1 s
            timer.schedule(timerTask, 3000, 1000L);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    六、登录接口生成 token
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        UserService userService;
    
        @Autowired
        TokenUtil tokenUtil;
    
        @PostMapping("/login")
        public Result<?> login(@RequestBody User user) {
       		 // 如果登陆校验成功,生成 token 返回前端
            if (userService.login(user))  {
                        String token = tokenUtil.createToken(user.getUserName());
                        TokenEntity tokenEntity = new TokenEntity(token, new Date(), tokenUtil.expireTime / 2);
                        tokenUtil.map.put(token, tokenEntity);
                        return Result.OK(user, token);
            }
            return Result.error("登录失败");
        }
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    Java on VS Code 8月更新|Spring 功能更新、构建工具改进及调试体验提升
    Jetson系统烧录环境搭建
    外卖项目(SpringBoot)--- 数据表设计、创建项目工程、登录模块
    项目型ERP系统哪家做得好?
    puppeteer的简单使用
    [黑马程序员Pandas教程]——Pandas数据类型
    android之TextView自由选择复制
    SpringBoot SpringSecurity 介绍(基于内存的验证)
    五种实现数据加密存储的方式,你选择哪一种
    蚂蚁集团SQLess 开源,与内部版有何区别?
  • 原文地址:https://blog.csdn.net/qq_41538097/article/details/127773921