JWT里有一个关键的东东,就是续期TOKEN,即TOKEN快过期时,刷新一个新的TOKEN给客户端.
办法如下:
1.后端生成TOKEN
import com.starmark.core.shiro.model.SecurityUser;
import com.starmark.core.shiro.model.UserLoginToken;
import com.starmark.core.shiro.util.JWTUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Objects;
public class JwtAuthFilter extends AuthenticatingFilter {
private final Logger log = LoggerFactory.getLogger(JwtAuthFilter.class);
//10分钟后刷新token
private static final int tokenRefreshInterval = 60 * 10;
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) //对于OPTION请求做拦截,不做token校验
return false;
return super.preHandle(request, response);
}
@Override
protected void postHandle(ServletRequest request, ServletResponse response) {
request.setAttribute("jwtShiroFilter.FILTERED", true);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (this.isLoginRequest(request, response)) {
return true;
}
Boolean afterFiltered = (Boolean) (request.getAttribute("jwtShiroFilter.FILTERED"));
if (BooleanUtils.isTrue(afterFiltered))
return true;
boolean allowed = false;
try {
allowed = executeLogin(request, response);
} catch (IllegalStateException e) { //not found any token
log.error("Not found any token");
} catch (Exception e) {
log.error("Error occurs when login", e);
}
return allowed || super.isPermissive(mappedValue);
}
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
String jwtToken = getAuthzHeader(servletRequest);
if (StringUtils.isNotBlank(jwtToken) && !JWTUtil.isTokenExpired(jwtToken))
return UserLoginToken.buildPassword(jwtToken, null, "jwt");
return null;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletResponse httpResponse = WebUtils.toHttp(servletResponse);
httpResponse.sendRedirect("/unauth");
return false;
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (token instanceof UserLoginToken && "jwt".equalsIgnoreCase(((UserLoginToken) token).getLoginType())) {
UserLoginToken jwtToken = (UserLoginToken) token;
boolean shouldRefresh = shouldTokenRefresh(Objects.requireNonNull(JWTUtil.getIssuedAt(jwtToken.getUsername())));
if (shouldRefresh) {
//生成新的TOKEN
SecurityUser user = (SecurityUser) subject.getPrincipal();
String newToken = JWTUtil.sign(user.getUserInfo().getId());
httpResponse.setHeader("x-auth-token", newToken);
}
}
return true;
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
log.error("Validate token fail, token:{}, error:{}", token.toString(), e.getMessage());
return false;
}
/**
* 获取TOKEN
* @param request 请求
* @return token
*/
private String getAuthzHeader(ServletRequest request) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
String header = httpRequest.getHeader("x-auth-token");
return StringUtils.removeStart(header, "Bearer ");
}
/**
* 判断是否需要刷新TOKEN
* @param issueAt token签发日期
* @return 是否需要刷新TOKEN
*/
private boolean shouldTokenRefresh(Date issueAt) {
LocalDateTime issueTime = LocalDateTime.ofInstant(issueAt.toInstant(), ZoneId.systemDefault());
return LocalDateTime.now().minusSeconds(tokenRefreshInterval).isAfter(issueTime);
}
}
原签发TOKEN后10分钟后刷新新的TOKEN
2.前端获取TOKEN
// 拦截响应response,并做一些错误处理
axios.interceptors.response.use((response) => {
if(response.status ===200 && response.data && response.data.code === 401) {
//console.log(window.location.origin);
window.location.href=window.location.origin+window.location.pathname+'#/login';
}
//获取返回的TOKEN
const token=response.headers['x-auth-token'];
if(token) {
//将续期的TOKEN存起来
localStorage.setItem("token",token) ;
}
// 这里是填写处理信息
return response;
}, (err) => { // 这里是返回状态码不为200时候的错误处理
console.log(err);
if(err && err.response) {
switch(err.response.data.code) {
case 400:
err.message = '请求错误';
break;
case 401:
err.message = '未授权,请登录';
break;
case 403:
err.message = '无权限';
break;
case 404:
err.message = `请求地址出错: ${err.response.config.url}`;
break;
case 408:
err.message = '请求超时';
break;
case 500:
err.message = '服务器内部错误';
break;
case 501:
err.message = '服务未实现';
break;
case 502:
err.message = '网关错误';
break;
case 503:
err.message = '服务不可用';
break;
case 504:
err.message = '网关超时';
break;
case 505:
err.message = 'HTTP版本不受支持';
break;
default:
}
}
Vue.prototype.$message.error(err.response.data.msg!=null?err.response.data.msg:err.message);
return Promise.reject(err)
});
注意一点,需要通过过滤器调整FITLER,增加Access-Control-Expose-Headers的输出,否则无法获取response中的header.
至此,JWT的TOKEN续期功能完成.