diff --git a/core/backend/src/main/java/io/dataease/auth/server/AuthServer.java b/core/backend/src/main/java/io/dataease/auth/server/AuthServer.java index 8ec0b31781..2f594c6039 100644 --- a/core/backend/src/main/java/io/dataease/auth/server/AuthServer.java +++ b/core/backend/src/main/java/io/dataease/auth/server/AuthServer.java @@ -26,7 +26,6 @@ import io.dataease.plugins.xpack.ldap.dto.response.ValidateResult; import io.dataease.plugins.xpack.ldap.service.LdapXpackService; import io.dataease.plugins.xpack.oidc.service.OidcXpackService; import io.dataease.service.sys.SysUserService; - import io.dataease.service.system.SystemParameterService; import io.dataease.websocket.entity.WsMessage; import io.dataease.websocket.service.WsService; @@ -38,15 +37,14 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - @RestController public class AuthServer implements AuthApi { @@ -203,11 +201,12 @@ public class AuthServer implements AuthApi { result.put("defaultPwd", DEFAULT_PWD); } } - + Long expireTime = System.currentTimeMillis() + JWTUtils.getExpireTime(); TokenInfo tokenInfo = TokenInfo.builder().userId(user.getUserId()).username(username).build(); String token = JWTUtils.sign(tokenInfo, realPwd); // 记录token操作时间 result.put("token", token); + result.put("expireTime", expireTime); ServletUtils.setToken(token); DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, user.getUserId(), null, null, null); authUserService.unlockAccount(username, ObjectUtils.isEmpty(loginType) ? 0 : loginType); diff --git a/core/backend/src/main/java/io/dataease/auth/util/JWTUtils.java b/core/backend/src/main/java/io/dataease/auth/util/JWTUtils.java index 38d4779b12..9817264a00 100644 --- a/core/backend/src/main/java/io/dataease/auth/util/JWTUtils.java +++ b/core/backend/src/main/java/io/dataease/auth/util/JWTUtils.java @@ -1,8 +1,8 @@ package io.dataease.auth.util; import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.JWTCreator.Builder; +import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; @@ -10,7 +10,10 @@ import com.google.gson.Gson; import io.dataease.auth.entity.TokenInfo; import io.dataease.auth.entity.TokenInfo.TokenInfoBuilder; import io.dataease.commons.model.OnlineUserModel; -import io.dataease.commons.utils.*; +import io.dataease.commons.utils.CommonBeanFactory; +import io.dataease.commons.utils.IPUtils; +import io.dataease.commons.utils.ServletUtils; +import io.dataease.commons.utils.TokenCacheUtils; import io.dataease.plugins.common.exception.DataEaseException; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -77,6 +80,13 @@ public class JWTUtils { return sign(tokenInfo, secret, true); } + public static Long getExpireTime() { + if (ObjectUtils.isEmpty(expireTime)) { + expireTime = Objects.requireNonNull(CommonBeanFactory.getBean(Environment.class)).getProperty("dataease.login_timeout", Long.class, 480L); + } + return expireTime * 60000L; + } + private static boolean tokenValid(OnlineUserModel model) { String token = model.getToken(); // 如果已经加入黑名单 则直接返回无效 @@ -84,10 +94,7 @@ public class JWTUtils { if (invalid) return false; Long loginTime = model.getLoginTime(); - if (ObjectUtils.isEmpty(expireTime)) { - expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L); - } - long expireTimeMillis = expireTime * 60000L; + long expireTimeMillis = getExpireTime(); // 如果当前时间减去登录时间小于超时时间则说明token未过期 返回有效状态 return System.currentTimeMillis() - loginTime < expireTimeMillis; @@ -133,10 +140,7 @@ public class JWTUtils { DataEaseException.throwException("MultiLoginError1"); } } - if (ObjectUtils.isEmpty(expireTime)) { - expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L); - } - long expireTimeMillis = expireTime * 60000L; + long expireTimeMillis = getExpireTime(); Date date = new Date(System.currentTimeMillis() + expireTimeMillis); Algorithm algorithm = Algorithm.HMAC256(secret); Builder builder = JWT.create() diff --git a/core/frontend/src/settings.js b/core/frontend/src/settings.js index 191d0e01f2..0e829ce793 100644 --- a/core/frontend/src/settings.js +++ b/core/frontend/src/settings.js @@ -1,5 +1,6 @@ module.exports = { TokenKey: 'Authorization', + TokenExpKey: 'de-login-login-exp', RefreshTokenKey: 'refreshauthorization', LinkTokenKey: 'LINK-PWD-TOKEN', title: 'DataEase', diff --git a/core/frontend/src/store/modules/user.js b/core/frontend/src/store/modules/user.js index 4bb201dd3e..66f806c407 100644 --- a/core/frontend/src/store/modules/user.js +++ b/core/frontend/src/store/modules/user.js @@ -1,5 +1,5 @@ import { login, logout, deLogout, getInfo, getUIinfo, languageApi } from '@/api/user' -import { getToken, setToken, removeToken, setSysUI } from '@/utils/auth' +import { getToken, setToken, removeToken, setSysUI, setTokenExp } from '@/utils/auth' import { resetRouter } from '@/router' import { format } from '@/utils/formatUi' import { getLanguage } from '@/lang/index' @@ -82,6 +82,7 @@ const actions = { commit('SET_TOKEN', data.token) commit('SET_LOGIN_MSG', null) setToken(data.token) + setTokenExp(data.expireTime) let passwordModified = true if (Object.prototype.hasOwnProperty.call(data, 'passwordModified')) { passwordModified = data.passwordModified diff --git a/core/frontend/src/utils/auth.js b/core/frontend/src/utils/auth.js index 680a502d86..98ff9b5bf3 100644 --- a/core/frontend/src/utils/auth.js +++ b/core/frontend/src/utils/auth.js @@ -2,6 +2,7 @@ import Cookies from 'js-cookie' import Config from '@/settings' import store from '@/store' const TokenKey = Config.TokenKey +const TokenExpKey = Config.TokenExpKey const IdTokenKey = Config.IdTokenKey @@ -22,11 +23,27 @@ export function getToken() { export function setToken(token) { return Cookies.set(TokenKey, token) } +export function setTokenExp(exp) { + if (exp) { + // return Cookies.set(TokenExpKey, exp) + return Cookies.set(TokenExpKey, new Date().getTime() + 5000) + } + return null +} + +export function tokenExp() { + const exp = Cookies.get(TokenExpKey) + if (exp && exp > 3000) { + return new Date().getTime() > (exp - 3000) + } + return false +} export function removeToken() { Cookies.remove(casSessionKey) Cookies.remove(IdTokenKey) Cookies.remove(AccessTokenKey) + Cookies.remove(TokenExpKey) return Cookies.remove(TokenKey) } diff --git a/core/frontend/src/utils/request.js b/core/frontend/src/utils/request.js index 9f4224a2e4..5bc5f02ba0 100644 --- a/core/frontend/src/utils/request.js +++ b/core/frontend/src/utils/request.js @@ -1,7 +1,7 @@ import axios from 'axios' import store from '@/store' import { $alert, $error } from './message' -import { getToken, getIdToken, setToken } from '@/utils/auth' +import { getToken, getIdToken, setToken, tokenExp } from '@/utils/auth' import Config from '@/settings' import i18n from '@/lang' import { tryShowLoading, tryHideLoading } from './loading' @@ -56,7 +56,6 @@ service.interceptors.request.use( if (idToken) { config.headers[Config.IdTokenKey] = idToken } - if (store.getters.token) { config.headers[TokenKey] = getToken() } @@ -74,7 +73,26 @@ service.interceptors.request.use( config.headers['Accept-Language'] = lang } config.loading && tryShowLoading(store.getters.currentPath) - + if (config.headers[TokenKey]) { + const logoutApiList = ['/api/auth/deLogout', '/api/auth/logout'] + if (tokenExp() && !logoutApiList.includes(config.url)) { + config['expCancel'] = null + config.cancelToken = new CancelToken(function executor(c) { + config['expCancel'] = c + }) + const message = i18n.t('login.expires') + $alert(message, () => { + store.dispatch('user/logout').then(() => { + location.reload() + }) + }, { + confirmButtonText: i18n.t('login.re_login'), + showClose: false + }) + config.expCancel('login.expires') + return config + } + } config.cancelToken = new CancelToken(function executor(c) { Vue.prototype.$currentHttpRequestList.set(config.url, c) }) @@ -104,10 +122,12 @@ service.interceptors.response.use(response => { } return response.data }, error => { - const config = error.response && error.response.config || error.config + let config = error.response && error.response.config || error.config const headers = error.response && error.response.headers || error.response || config && config.headers - config.loading && tryHideLoading(store.getters.currentPath) - + config?.loading && tryHideLoading(store.getters.currentPath) + if (!config && !headers && error.code === 'ERR_CANCELED' && error.message === 'login.expires') { + config = { hideMsg: true } + } let msg = '' if (error.response) { @@ -119,7 +139,7 @@ service.interceptors.response.use(response => { if (msg.length > 600) { msg = msg.slice(0, 600) } - !config.hideMsg && (!headers['authentication-status']) && !msg?.startsWith('MultiLoginError') && $error(msg) + !config?.hideMsg && (!headers['authentication-status']) && !msg?.startsWith('MultiLoginError') && $error(msg) return Promise.reject(config.url === '/dataset/table/sqlPreview' ? msg : error) }) const checkDownError = response => {