实现原理:
和access_token一样, 在request请求里加入refresh_token标识,给access_token设置短时间的期限(例如2小时),给refresh_token设置长时间的期限(例如七天)。当活动用户(拥有access_token)发起request时,在权限验证里,对于requeset的header包含的access_token、refresh_token分别进行验证:
1、 access_token过期, 权限认证失败, 用户跳转到登录页面
2、access_token没过期, 即通过权限验证
a. 当access_token签发时间 < 当前时间 < (签发时间+((token过期时间-token签发时间) / 3 * 2)), 不刷新token
b. 当(签发时间+((token过期时间-token签发时间) / 3 * 2)) < 当前时间 < token过期时间, 刷新token并返回给前端, 再在客户端缓存里更新access_token值
3、access_token过期, refresh_token过期即权限验证失败, 用户跳转到登录页面
下面展示一下关键代码:
一、登录生成token的时候加入refresh标识
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("==>>LoginFilter.successfulAuthentication start...");
AuthUserTokenBO user = (AuthUserTokenBO) authResult.getPrincipal();
//将userId存入session(程序不再使用session)
// request.getSession().setAttribute(Constants.CURRENT_USER, user.getAuthToken().getUid());
generateAccessToken(response, user);
generateRefreshToken(response, user);
String encryptStr = JwUtil.encrypt(String.valueOf(user.getAuthToken().getUid()));
response.addHeader(Constants.LOGIN_ID_SECRET, encryptStr);
response.addHeader(Constants.LOGIN_ID, user.getUsername());
response.getWriter().write(processSuccessMsg(Constants.SUCCESS_LOGIN));
LoginLogEvent event = new LoginLogEvent(this, user.getUsername(), request);
event.setEventTypeEnum(LoginEventTypeEnum.LOGIN);
event.setIsSuccess(true);//
applicationContext.publishEvent(event);
try {
loginFailedBizService.doVoidRecord(user.getUsername(), JwUtil.getRealIpAddress(request));
} catch (JwBlogException e) {
log.error(">>LoginFilter.successfulAuthentication exec failed, e:\r\n", e);
}
log.info("==>>LoginFilter.successfulAuthentication end...");
}二、在权限验证环节,对于access_token、refresh_token设置不同的时间期限, 计算是否要刷新access_token, 再根据判断结果返回给前端。
private JwAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response,
UserBO userBO) {
log.info("==>>JwtAuthenticationFilter.getAuthentication start...");
List<GrantedAuthority> authorities = new ArrayList<>();
String accessToken = request.getHeader(Constants.ACCESS_TOKEN);
if (StringUtils.isBlank(accessToken)) {
accessToken = request.getParameter(Constants.ACCESS_TOKEN);
}
String refreshToken = request.getHeader(Constants.ACCESS_TOKEN);
if (StringUtils.isBlank(refreshToken)) {
refreshToken = request.getParameter(Constants.ACCESS_TOKEN);
}
log.info("==>>JwtAuthenticationFilter.getAuthentication access token {}...", accessToken);
log.info("==>>JwtAuthenticationFilter.getAuthentication refresh token {}...", refreshToken);
if (StringUtils.isNotBlank(accessToken)) {
Long oid = null;
try {
Claims claims = JwtUtils.parse(accessToken);
Long refresgOid = null;
Date refreshExpiredDate = null;
if (StringUtils.isNotBlank(refreshToken)) {
Claims refreshClaims = JwtUtils.parse(refreshToken);
refresgOid = Long.parseLong(String.valueOf(refreshClaims.get(USER_KEY)));
refreshExpiredDate = refreshClaims.getExpiration();
}
oid = Long.parseLong(String.valueOf(claims.get(USER_KEY)));
request.setAttribute(USER_KEY, oid);
String loginIdCacheKey = MessageFormat.format(CacheKeyConstants.LOGIN_USER_STATUS, oid);
boolean isLogin = (Boolean) jwCacheStore.get(loginIdCacheKey).orElse(false);
// 解决服务器每次重启缓存失效,但是jwt有效的问题,调试时可以注释
if (!Boolean.TRUE.equals(isLogin)) {
log.error("======>>user logout failed, user does not login.");
return null;
}
// 验证是否失效
String invalidTokenKey = MessageFormat.format(CacheKeyConstants.INVALID_TOKEN, accessToken);
if (jwCacheStore.hasKey(invalidTokenKey)) {
return null;
}
// token签发时间
long issuedAt = claims.getIssuedAt().getTime();
// 当前时间
long currentTimeMillis = System.currentTimeMillis();
// token过期时间
long expirationTime = claims.getExpiration().getTime();
// 1. 签发时间 < 当前时间 < (签发时间+((token过期时间-token签发时间) / 3 * 2)) 不刷新token
// 2. (签发时间+((token过期时间-token签发时间) / 3 * 2)) < 当前时间 < token过期时间 刷新token并返回给前端
// 3. tokne过期时间 < 当前时间 跳转登录,重新登录获取token
// 验证token时间有效性
if (!isReceiveMsgNotofy(request)) {
if (Objects.equals(oid, refresgOid) && refreshExpiredDate.getTime() > currentTimeMillis) {
if ((issuedAt + ((expirationTime - issuedAt) / 3 * 2)) < currentTimeMillis
&& currentTimeMillis < expirationTime) {
log.info("JWT token (url=[{}]) will expire in {} Millis, system will refreshToken.",
request.getServletPath(), (expirationTime - currentTimeMillis));
String refreshAccessToken = JwtUtils.sign(claims, Long.parseLong(accessTokenExpireSec) * 1000);
// 将token放入响应头中
response.setContentType(Constants.CONTENT_TYPE_JSON);
response.addHeader(Constants.REFRESH_ACCESS_TOKEN, refreshAccessToken);
}
}
}
} catch (Exception e) {
log.info("==>>JwtAuthenticationFilter.getAuthentication exec failed, e\r\n", e);
return null;
}
// 从数据库中取出用户信息
AdminBizService adminBizService = SpringUtil.getBean(AdminBizService.class);
try {
AdminBO admin = adminBizService.queryAdminByOid(oid);
// 实际上不会为null,直接抛出异常
if (admin == null) {
return null;
}
userBO.setName(admin.getUsername());
Map param = new HashMap();
param.put(Constants.LOGIN_IP, JwUtil.getRealIpAddress(request));
authorities.add(
new SimpleGrantedAuthority(Constants.ROLE_PREFIX + Constants.ADMIN.toUpperCase(Locale.ROOT)));
// 这里直接注入角色,因为JWT已经验证了用户合法性,所以principal和credentials直接为null即可
return new JwAuthenticationToken(authorities, admin.getUsername(), null, param);
} catch (JwBlogException e) {
log.info("==>>JwtAuthenticationFilter.getAuthentication exec failed, e\r\n", e);
return null;
}
}
log.info("==>>JwtAuthenticationFilter.getAuthentication start...");
return null;
}
苏ICP备16040035号-5