java token 超时_token超时刷新策略(附源码)

需求场景

我们在做用户中心时,对于登录用户签发其对应的token,对token设置他的固定有效期时间。可是我们通常会遇到一个这样的问题:在有效期内用户携带token访问没问题,当过了有效期后token失效,用户需要重新登录获取新的token。

想象以下,假如生产环境中我把token时间设置为15分钟,用户每隔15分钟需要重新登录一次,这种体验可能会被用户打死。

实现目标

一个优秀的体验应该是:活跃的用户应该在无感知的情况下在token失效后获取到新的token,携带这个新的token进行访问,而长时间不活跃的用户应该在token失效后需要重新进行登录认证。也就是所谓的token刷新机制。

关于JWT的基本认识和使用,这里不再做详细赘述,有疑问的小可爱可以先阅读以下两篇文章:

看完这篇 Session、Cookie、Token,和面试官扯皮就没问题了

Springboot+JWT实战(附源码)

本篇文章我们整合Springboot、Redis、JWT对token刷新机制做详细讲解。

设计思路

用户登录时,为登录用户签发JWT,并设置JWT的有效时间,同时将该用户的JWT保存至redis缓存中,并设置缓存的有效期,缓存中保存的JWT的有效期要大于JWT本身的有效时间。其中:缓存时间-JWT有效时间=token可刷新时间

java token 超时_token超时刷新策略(附源码)_第1张图片

用户进行接口请求时,需要携带token,而此时进行接口请求时通常有以下几种状态:

未携带token传入:此时直接返回token为空,权限不足;

错误token传入:此时直接返回token错误,权限不足;

正常token传入:放行。

正常token但已过期:查询redis中该token是否存在,如果缓存中存在则刷新token,并将刷新后的token重新放入redis,使用刷新后的token请求资源;缓存中不存在,则token刷新期限已过,用户需重新登录。

代码实现

本次JWT刷新机制Github地址:https://github.com/bailele1995/springboot-jjwt.git

本文小码仔仅贴出设计思路中涉及的关键部分代码作以说明讲解,有兴趣的小可爱可以拉取GitHub完整代码进行研究。

1.用户登录

public ResultDTO login(String name, String password) {

Map tokenMap = new HashMap<>();

String token = null;

try {

User user = userMapper.findByUsername(name);

//查询用户登录的账号是否存在

if(user == null){

return ResultDTO.failure(new ResultError(UserError.EMP_IS_NULL_EXIT));

}else{

//校验用户密码是否正确

if(!user.getPassword().equals(password)){

return ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));

}else {

// 获取token,将 user id 、userName保存到 token 里面

token = JwtUtil.sign(user.getUsername(),user.getId(),user.getPassword());

//将用户token保存至缓存中,并设置有效时间

redisUtils.set("userToken-" + user.getId(), token, 2L * 60);

}

}

//校验token是否为空

if(StringUtils.isEmpty(token)){

return ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));

}

tokenMap.put("token", token);

tokenMap.put("user",user);

} catch (Exception e) {

e.printStackTrace();

}

//将登录用户信息和token返回给前端

return ResultDTO.success(tokenMap);

}

复制代码

在该部分,对登录用户进行校验,并生成token,将生成的token保存至redis中。生成的token和用户信息返回给前端。

2.拦截器

public class AuthenticationInterceptor implements HandlerInterceptor {

@Autowired

private RedisUtils redisUtils;

@Autowired

UserMapper userMapper;

@Override

public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {

// 从 http 请求头中取出 token

String token = httpServletRequest.getHeader("token");

// 如果不是映射到方法直接通过

if(!(object instanceof HandlerMethod)){

return true;

}

HandlerMethod handlerMethod=(HandlerMethod)object;

Method method=handlerMethod.getMethod();

PrintWriter out = null ;

//检查有没有需要用户权限的注解

if (method.isAnnotationPresent(TokenRequired.class)) {

TokenRequired userLoginToken = method.getAnnotation(TokenRequired.class);

if (userLoginToken.required()) {

// 执行认证

//1.如果token为空,返回token为空

if (token == null) {

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(new ResultError(UserError.NONE_TOKEN))));

return false;

}

// 2.获取 token 中的 user id,校验token

String userId;

try {

userId = JWT.decode(token).getClaim("userId").asString();

} catch (JWTDecodeException j) {

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(new ResultError(UserError.TOKEN_CHECK_ERROR))));

return false;

}

//3.查询token中登录用户数据库中是否存在

User user = userMapper.findUserById(userId);

if (user == null) {

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(new ResultError(UserError.EMP_IS_NULL_EXIT))));

return false;

}

// 验证 token

try {

if (!JwtUtil.verity(token, user.getPassword())) {

//如果token检验失败,查询redis中是否存在,redis为空,用户重新登录

if (redisUtils.get("userToken-" + user.getId()) == null) {

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(new ResultError(UserError.TOKEN_IS_VERITYED))));

return false;

}

//如果redis不为空,刷新token

String reToken = JwtUtil.sign(user.getUsername(),user.getId(),user.getPassword());

//将刷新后的token保存在redis中

redisUtils.set("userToken-" + user.getId(), reToken, 2L * 60);

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(reToken,new ResultError(UserError.TOKEN_IS_EXPIRED))));

return false;

}

} catch (Exception e) {

out = httpServletResponse.getWriter();

out.append(JSON.toJSONString(ResultDTO.failure(new ResultError(UserError.TOKEN_CHECK_ERROR))));

return false;

}

}

}

return true;

}

@Override

public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

}

@Override

public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

}

}

复制代码

该拦截器请求业务处理流程如下:

判断用户请求资源是否携带token,未携带则权限不足;

携带token则从token中获取用户id,并校验数据库,查看该用户是否存在;不存在则token无效不予放行;

校验token是否过期,如果是正常token且未过期,放行。

正常token但已过期:查询redis中该token是否存在,如果缓存中存在则刷新token,并将刷新后的token重新放入redis,使用刷新后的token请求资源;缓存中不存在,则token刷新期限已过,用户需重新登录。

总结

回顾一下本次JWT刷新机制的基本业务判断流程:

用户登录,生成token,并将token写入缓存;

用户访问页面,前端请求相关接口,经过拦截器,拦截器中从 http 请求头中取出 token;

检验token是否为空,如果token为空,访问失败;token不为空,则查询用户信息并校验token;

token不为空,且用户存在,则token正确,进行token过期校验;

token已过期,查询缓存中token是否存在,存在进行刷新生成新的token,返回给前端,并将新的token放入缓存;不存在则用户需重新登录。

最后

最后,感谢各位的阅读。文章的目的是记录和分享,若文中出现明显纰漏也欢迎指出,我们一起在探讨中学习。不胜感激 !

如果你觉得本文对你有用,那就给个「赞」吧,你的鼓励和支持是我前进路上的动力~

欢迎关注我的微信公众号【小码仔】,我们一起探讨代码与人生。

你可能感兴趣的:(java,token,超时)