小程序后端token生成机制以及与前端交互处理
一、token的生成
/**
* 校验和创建token
* @param userId
* @param sign
* @param timestamp
* @return
*/
public TokenRespVO checkoutAndCreateToken(String token,String userId,
String sign,String timestamp) {
//step1:判断之前是否获取过token,如果没有获取,生成token。如果获取了,判断token的剩余时间是否小于1分钟,如果大於则返回之前的token
long ttl = redisDao.ttl(RedisKeyPrefix.TOKEN + token);
if (ttl > Constants.REPEATED_REQUEST_TIME) {
TokenRespVO resp = setNotExpireToken(token, ttl);
return resp;
}
//step2:判断用户是否存在,不存在不让获取token
User user = commonUserService.checkVaildUser(userId);
//step3:判断用户的登录有效时间是否过期
long sessionTimeOut = user.getSessionTimeOut().toInstant(ZoneOffset.of("+8")).toEpochMilli();
long currentTimeMillis = System.currentTimeMillis();
if (sessionTimeOut <= currentTimeMillis) {
throw new ServiceException(ExceptionCode.ACCOUNT_EXPIRE, "账号已过期,请重新登录!");
}
//step4:判断签名是否有效
String digest = null;
try {
digest = Md5Tool.getMD5Code(userId + timestamp + user.getOpenId()).toUpperCase();
} catch (Exception e) {
logger.error("checkoutAndCreateToken md5 fail:{}",e);
}
if (!sign.equals(digest)) {
throw new ServiceException(ExceptionCode.SIGN_INVAILD, "签名无效");
}
//step5:生成token和token的过期时间
TokenRespVO newToken = createToken(userId, user);
return newToken;
}
/**
* 返回没有过期的token
* @param token
* @param ttl
* @return
*/
private TokenRespVO setNotExpireToken(String token, Long ttl) {
TokenRespVO resp = new TokenRespVO();
resp.setTokenValue(token);
long currentTime = System.currentTimeMillis();
long expireTime = currentTime + ttl * 1000;
resp.setExpireTime(expireTime);
return resp;
}
/**
* 创建token
* @param userId
* @param user
* @return
*/
public TokenRespVO createToken(String userId, User user) {
//step5.1:判断数据库中保存的token是否过期
String token = user.getToken();
if (token != null) {
long ttl = redisDao.ttl(RedisKeyPrefix.TOKEN + token);
if (ttl > Constants.REPEATED_REQUEST_TIME) {
TokenRespVO resp = setNotExpireToken(token, ttl);
return resp;
}
}
//step5.2:生成token
TokenRespVO resp = new TokenRespVO();
String token = RandomUtil.generateLowerString(16);
redisDao.setValEx(RedisKeyPrefix.TOKEN + token, userId, Constants.TOKEN_EXPIRE_TIME);
resp.setTokenValue(token);
long currentTime = System.currentTimeMillis();
long expireTime = currentTime + Constants.TOKEN_EXPIRE_TIME * 1000;
resp.setExpireTime(expireTime);
//step5.3:更新数据库中的token
user.setToken(token);
user.setUpdateTime(LocalDateTime.now());
userDao.updateUser(user);
return resp;
}
/**
* 返回token信息的VO
* @author mike_z
*
*/
public class TokenRespVO {
/**
* token的值
*/
private String tokenValue;
/**
* token过期时间,单位为毫秒
*/
private long expireTime;
public String getTokenValue() {
return tokenValue;
}
public void setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
@Override
public String toString() {
return "TokenRespVO [tokenValue=" + tokenValue + ", expireTime=" + expireTime + "]";
}
}
二、token的拦截
/**
* 认证的拦截器
*
*/
@Component
public class AuthInterceptor implements HandlerInterceptor{
@Autowired
private RedisDao redisDao;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader(Constants.REQUEST_HEADER);
if (StringUtils.isNoneBlank(wmkjToken)) {
String userId = redisDao.getVal(RedisKeyPrefix.TOKEN + token);
if (userId != null) {
request.setAttribute("userId", userId);
return true;
}
}
//响应Json数据
responseJson(response);
return false;
}
/**
* 响应Json数据
* @param response
* @throws IOException
*/
private void responseJson(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.append(JsonUtil.toJSon(ResData.fail(ExceptionCode.INVALID_TOKEN, "无效的token")));
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
三、token在前端和后端的使用
前端每次请求的时候把token放在请求头中,后端通过拦截获取请求头里的token,然后查询redis,获取token对应的值,如果为null,则token已经过期了,返回token过期的code给到前端。前端通过判断返回值的code为token过期,则重新调获取token的接口,将返回的token写入缓存,然后刷新当前页面。后端拿到token则鉴权通过。