feat: 长时间无操作则token自动失效

This commit is contained in:
fit2cloud-chenyw 2021-03-10 14:25:26 +08:00
parent 2a766461ca
commit e5941ee58b
7 changed files with 67 additions and 30 deletions

View File

@ -6,6 +6,9 @@ import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.service.AuthUserService; import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.util.JWTUtils; import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.utils.CommonBeanFactory; import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.utils.ServletUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -23,6 +26,8 @@ public class JWTFilter extends BasicHttpAuthenticationFilter {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
public final static String expireMessage = "login token is expire";
/*@Autowired /*@Autowired
private AuthUserService authUserService;*/ private AuthUserService authUserService;*/
@ -46,7 +51,10 @@ public class JWTFilter extends BasicHttpAuthenticationFilter {
HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader("Authorization"); String authorization = httpServletRequest.getHeader("Authorization");
// 当没有出现登录超时 且需要刷新token 则执行刷新token // 当没有出现登录超时 且需要刷新token 则执行刷新token
if (!JWTUtils.loginExpire(authorization) && JWTUtils.needRefresh(authorization)){ if (JWTUtils.loginExpire(authorization)){
throw new AuthenticationException(expireMessage);
}
if (JWTUtils.needRefresh(authorization)){
authorization = refreshToken(request, response); authorization = refreshToken(request, response);
} }
JWTToken token = new JWTToken(authorization); JWTToken token = new JWTToken(authorization);
@ -67,7 +75,12 @@ public class JWTFilter extends BasicHttpAuthenticationFilter {
boolean loginSuccess = executeLogin(request, response); boolean loginSuccess = executeLogin(request, response);
return loginSuccess; return loginSuccess;
} catch (Exception e) { } catch (Exception e) {
response401(request, response); if (e instanceof AuthenticationException && StringUtils.equals(e.getMessage(), expireMessage)){
responseExpire(request, response);
}else {
response401(request, response);
}
} }
} }
return false; return false;
@ -129,4 +142,16 @@ public class JWTFilter extends BasicHttpAuthenticationFilter {
LOGGER.error(e.getMessage()); LOGGER.error(e.getMessage());
} }
} }
private void responseExpire(ServletRequest req, ServletResponse resp) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
httpServletResponse.addHeader("Access-Control-Expose-Headers", "authentication-status");
httpServletResponse.setHeader("authentication-status", "login_expire");
httpServletResponse.setStatus(401);
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
}
} }

View File

@ -6,23 +6,23 @@ import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import io.dataease.auth.entity.TokenInfo; import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.filter.JWTFilter;
import io.dataease.commons.utils.CommonBeanFactory; import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.utils.ServletUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import javax.servlet.http.HttpServletResponse;
import java.util.Date; import java.util.Date;
public class JWTUtils { public class JWTUtils {
// token过期时间5min (过期会自动刷新续命 目的是避免一直都是同一个token ) // token过期时间1min (过期会自动刷新续命 目的是避免一直都是同一个token )
private static final long EXPIRE_TIME = 1*60*1000; private static final long EXPIRE_TIME = 1*60*1000/2;
// 登录间隔时间10min 超过这个时间强制重新登录 // 登录间隔时间10min 超过这个时间强制重新登录
private static final long Login_Interval = 2*60*1000; private static final long Login_Interval = 20*60*1000;
/** /**
@ -38,17 +38,12 @@ public class JWTUtils {
.withClaim("username", tokenInfo.getUsername()) .withClaim("username", tokenInfo.getUsername())
.withClaim("userId", tokenInfo.getUserId()) .withClaim("userId", tokenInfo.getUserId())
.build(); .build();
DecodedJWT jwt = verifier.verify(token); verifier.verify(token);
//Long lastLoginTime = jwt.getClaim("lastLoginTime").asLong(); if (loginExpire(token)){
Long lastOperateTime = tokenLastOperateTime(token);
long now = System.currentTimeMillis();
if (now - lastOperateTime > Login_Interval){
// 登录超时 // 登录超时
HttpServletResponse response = ServletUtils.response(); throw new AuthenticationException(JWTFilter.expireMessage);
response.addHeader("Access-Control-Expose-Headers", "authentication-status");
response.setHeader("authentication-status", "login_expire");
// 前端拦截 登录超时状态 直接logout // 前端拦截 登录超时状态 直接logout
return false; //return false;
} }
return true; return true;
} }
@ -143,6 +138,7 @@ public class JWTUtils {
public static void addTokenExpire(String token){ public static void addTokenExpire(String token){
CacheManager cacheManager = CommonBeanFactory.getBean(CacheManager.class); CacheManager cacheManager = CommonBeanFactory.getBean(CacheManager.class);
Cache tokens_expire = cacheManager.getCache("tokens_expire"); Cache tokens_expire = cacheManager.getCache("tokens_expire");
tokens_expire.put(token, System.currentTimeMillis()); long now = System.currentTimeMillis();
tokens_expire.put(token, now);
} }
} }

View File

@ -80,7 +80,9 @@ export default {
password: '密码', password: '密码',
any: '随便填', any: '随便填',
thirdparty: '第三方登录', thirdparty: '第三方登录',
thirdpartyTips: '本地不能模拟,请结合自己业务进行模拟!!!' thirdpartyTips: '本地不能模拟,请结合自己业务进行模拟!!!',
expires: '登录token过期请重新登录',
tokenError: 'token错误请重新登录'
}, },
commons: { commons: {
upload: '上传', upload: '上传',

View File

@ -42,6 +42,9 @@ const mutations = {
}, },
SET_PERMISSIONS: (state, permissions) => { SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions state.permissions = permissions
},
SET_LOGIN_MSG: (state, msg) => {
state.loginMsg = msg
} }
} }
@ -53,6 +56,7 @@ const actions = {
login({ username: username.trim(), password: password }).then(response => { login({ username: username.trim(), password: password }).then(response => {
const { data } = response const { data } = response
commit('SET_TOKEN', data.token) commit('SET_TOKEN', data.token)
commit('SET_LOGIN_MSG', null)
setToken(data.token) setToken(data.token)
resolve() resolve()
}).catch(error => { }).catch(error => {
@ -117,6 +121,9 @@ const actions = {
commit('RESET_STATE') commit('RESET_STATE')
resolve() resolve()
}) })
},
setLoginMsg({ commit, msg }) {
commit('SET_LOGIN_MSG', msg)
} }
} }

View File

@ -1,7 +1,7 @@
import { MessageBox, Message } from 'element-ui' import { MessageBox, Message } from 'element-ui'
import i18n from '@/lang'
export const $alert = (message, callback, options) => { export const $alert = (message, callback, options) => {
const title = this.$t('common.message_box.alert') const title = i18n.t('common.message_box.alert')
MessageBox.alert(message, title, options).then(() => { MessageBox.alert(message, title, options).then(() => {
callback() callback()
}) })
@ -9,12 +9,12 @@ export const $alert = (message, callback, options) => {
export const $confirm = (message, callback, options = {}) => { export const $confirm = (message, callback, options = {}) => {
const defaultOptions = { const defaultOptions = {
confirmButtonText: this.$t('common.button.ok'), confirmButtonText: i18n.t('common.button.ok'),
cancelButtonText: this.$t('common.button.cancel'), cancelButtonText: i18n.t('common.button.cancel'),
type: 'warning', type: 'warning',
...options ...options
} }
const title = this.$t('common.message_box.confirm') const title = i18n.t('common.message_box.confirm')
MessageBox.confirm(message, title, defaultOptions).then(() => { MessageBox.confirm(message, title, defaultOptions).then(() => {
callback() callback()
}) })

View File

@ -4,7 +4,7 @@ import store from '@/store'
import { $alert, $error } from './message' import { $alert, $error } from './message'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import Config from '@/settings' import Config from '@/settings'
import i18n from '@/lang'
import { tryShowLoading, tryHideLoading } from './loading' import { tryShowLoading, tryHideLoading } from './loading'
// import router from '@/router' // import router from '@/router'
@ -44,16 +44,19 @@ service.interceptors.request.use(
const checkAuth = response => { const checkAuth = response => {
// 请根据实际需求修改 // 请根据实际需求修改
if (response.headers['authentication-status'] === 'invalid' || response.status === 401) {
const message = this.$t('login.expires') if (response.headers['authentication-status'] === 'login_expire') {
const message = i18n.t('login.expires')
store.dispatch('user/setLoginMsg', message)
$alert(message, () => { $alert(message, () => {
store.dispatch('user/logout').then(() => { store.dispatch('user/logout').then(() => {
location.reload() location.reload()
}) })
}) })
} }
if (response.headers['authentication-status'] === 'login_expire') {
const message = this.$t('login.expires') if (response.headers['authentication-status'] === 'invalid' || response.status === 401) {
const message = i18n.t('login.tokenError')
$alert(message, () => { $alert(message, () => {
store.dispatch('user/logout').then(() => { store.dispatch('user/logout').then(() => {
location.reload() location.reload()

View File

@ -79,8 +79,12 @@ export default {
}, },
loading: false, loading: false,
passwordType: 'password', passwordType: 'password',
redirect: undefined, redirect: undefined
msg: null }
},
computed: {
msg() {
return this.$store.state.user.loginMsg
} }
}, },
watch: { watch: {