一、简介

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
	}
}

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;
}
三、新增 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;
        }
    }
}
四、拦截器中校验 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;
    }
}
五、定时任务删除 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);
    }
六、登录接口生成 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("登录失败");
    }
}    
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐