小程序後端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則鑑權通過。