diff --git a/backend/src/main/java/io/dataease/auth/filter/F2CDocFilter.java b/backend/src/main/java/io/dataease/auth/filter/F2CDocFilter.java index d5d8438c3a..ef1c304810 100644 --- a/backend/src/main/java/io/dataease/auth/filter/F2CDocFilter.java +++ b/backend/src/main/java/io/dataease/auth/filter/F2CDocFilter.java @@ -79,12 +79,7 @@ public class F2CDocFilter extends AccessControlFilter { if (StringUtils.isBlank(authorization)) { return false; } - if (JWTUtils.loginExpire(authorization)) { - return false; - } - if (JWTUtils.needRefresh(authorization)) { - authorization = refreshToken(authorization); - } + TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(authorization); AuthUserService authUserService = CommonBeanFactory.getBean(AuthUserService.class); SysUserEntity user = authUserService.getUserById(tokenInfo.getUserId()); @@ -96,20 +91,6 @@ public class F2CDocFilter extends AccessControlFilter { return verify; } - private String refreshToken(String token) throws Exception { - TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token); - AuthUserService authUserService = CommonBeanFactory.getBean(AuthUserService.class); - SysUserEntity user = authUserService.getUserById(tokenInfo.getUserId()); - if (user == null) { - DataEaseException.throwException(Translator.get("i18n_not_find_user")); - } - String password = user.getPassword(); - Algorithm algorithm = Algorithm.HMAC256(password); - JWTUtils.verifySign(algorithm, token); - String newToken = JWTUtils.sign(tokenInfo, password); - return newToken; - } - @Override protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception { HttpServletResponse response = (HttpServletResponse) res; diff --git a/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java b/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java index 0d70181289..83c73fc1ca 100644 --- a/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java +++ b/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java @@ -1,24 +1,18 @@ package io.dataease.auth.filter; -import com.auth0.jwt.algorithms.Algorithm; import io.dataease.auth.entity.ASKToken; import io.dataease.auth.entity.JWTToken; -import io.dataease.auth.entity.SysUserEntity; -import io.dataease.auth.entity.TokenInfo; + import io.dataease.auth.handler.ApiKeyHandler; -import io.dataease.auth.service.AuthUserService; -import io.dataease.auth.util.JWTUtils; -import io.dataease.commons.utils.CommonBeanFactory; + import io.dataease.commons.utils.LogUtil; import io.dataease.commons.utils.TokenCacheUtils; -import io.dataease.exception.DataEaseException; -import io.dataease.i18n.Translator; + import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; @@ -30,7 +24,6 @@ import javax.servlet.http.HttpServletResponse; public class JWTFilter extends BasicHttpAuthenticationFilter { - private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); public final static String expireMessage = "Login token is expire."; @@ -69,14 +62,7 @@ public class JWTFilter extends BasicHttpAuthenticationFilter { if (TokenCacheUtils.invalid(authorization)) { throw new AuthenticationException(expireMessage); } - // 当没有出现登录超时 且需要刷新token 则执行刷新token - if (JWTUtils.loginExpire(authorization)) { - TokenCacheUtils.remove(authorization); - throw new AuthenticationException(expireMessage); - } - if (JWTUtils.needRefresh(authorization)) { - authorization = refreshToken(request, response); - } + JWTToken token = new JWTToken(authorization); Subject subject = getSubject(request, response); // 提交给realm进行登入,如果错误他会抛出异常并被捕获 @@ -110,28 +96,6 @@ public class JWTFilter extends BasicHttpAuthenticationFilter { } - private String refreshToken(ServletRequest request, ServletResponse response) throws Exception { - // 获取AccessToken(Shiro中getAuthzHeader方法已经实现) - String token = this.getAuthzHeader(request); - // 获取当前Token的帐号信息 - TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token); - AuthUserService authUserService = CommonBeanFactory.getBean(AuthUserService.class); - SysUserEntity user = authUserService.getUserById(tokenInfo.getUserId()); - if (user == null) { - DataEaseException.throwException(Translator.get("i18n_not_find_user")); - } - String password = user.getPassword(); - Algorithm algorithm = Algorithm.HMAC256(password); - JWTUtils.verifySign(algorithm, token); - String newToken = JWTUtils.sign(tokenInfo, password); - // 设置响应的Header头新Token - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - httpServletResponse.addHeader("Access-Control-Expose-Headers", "RefreshAuthorization"); - httpServletResponse.setHeader("RefreshAuthorization", newToken); - return newToken; - } - - /** * 对跨域提供支持 */ diff --git a/backend/src/main/java/io/dataease/auth/util/JWTUtils.java b/backend/src/main/java/io/dataease/auth/util/JWTUtils.java index fc297d1e17..a541433842 100644 --- a/backend/src/main/java/io/dataease/auth/util/JWTUtils.java +++ b/backend/src/main/java/io/dataease/auth/util/JWTUtils.java @@ -4,7 +4,6 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.JWTCreator.Builder; import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.Verification; import io.dataease.auth.entity.TokenInfo; @@ -19,10 +18,8 @@ import java.util.Date; public class JWTUtils { - // token过期时间1min (过期会自动刷新续命 目的是避免一直都是同一个token ) - private static final long EXPIRE_TIME = 1 * 60 * 1000; - // 登录间隔时间10min 超过这个时间强制重新登录 - private static long Login_Interval; + + private static Long expireTime; /** * 校验token是否正确 @@ -65,62 +62,24 @@ public class JWTUtils { return tokenInfoBuilder.build(); } - public static boolean needRefresh(String token) { - Date exp = JWTUtils.getExp(token); - Long advanceTime = 5000L; - return (new Date().getTime() + advanceTime) >= exp.getTime(); - } - /** - * 当前token是否登录超时 - * - * @param token - * @return - */ - public static boolean loginExpire(String token) { - if (Login_Interval == 0) { - // 默认超时时间是8h - Long minute = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, - 8 * 60L); - // 分钟换算成毫秒 - Login_Interval = minute * 1000 * 60; - } - Long lastOperateTime = tokenLastOperateTime(token); - boolean isExpire = true; - if (lastOperateTime != null) { - Long now = System.currentTimeMillis(); - isExpire = now - lastOperateTime > Login_Interval; - } - return isExpire; - } - - public static Date getExp(String token) { - try { - DecodedJWT jwt = JWT.decode(token); - return jwt.getClaim("exp").asDate(); - } catch (JWTDecodeException e) { - e.printStackTrace(); - return null; - } - } - - /** - * 生成签名,5min后过期 - * * @param tokenInfo 用户信息 * @param secret 用户的密码 * @return 加密的token */ public static String sign(TokenInfo tokenInfo, String secret) { try { - Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); + if (ObjectUtils.isEmpty(expireTime)) { + expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L); + } + long expireTimeMillis = expireTime * 60000L; + Date date = new Date(System.currentTimeMillis() + expireTimeMillis); Algorithm algorithm = Algorithm.HMAC256(secret); Builder builder = JWT.create() .withClaim("username", tokenInfo.getUsername()) .withClaim("userId", tokenInfo.getUserId()); String sign = builder.withExpiresAt(date).sign(algorithm); return sign; - } catch (Exception e) { return null; } @@ -143,7 +102,6 @@ public class JWTUtils { } else { verifier = JWT.require(algorithm).withClaim("resourceId", resourceId).withClaim("userId", userId).build(); } - try { verifier.verify(token); return true; @@ -152,16 +110,5 @@ public class JWTUtils { } } - /** - * 获取当前token上次操作时间 - * - * @param token - * @return - */ - public static Long tokenLastOperateTime(String token) { - DecodedJWT jwt = JWT.decode(token); - Date expiresAt = jwt.getExpiresAt(); - return expiresAt.getTime(); - } } diff --git a/backend/src/main/java/io/dataease/commons/utils/TokenCacheUtils.java b/backend/src/main/java/io/dataease/commons/utils/TokenCacheUtils.java index 0bcec3b8c8..32b68267ab 100644 --- a/backend/src/main/java/io/dataease/commons/utils/TokenCacheUtils.java +++ b/backend/src/main/java/io/dataease/commons/utils/TokenCacheUtils.java @@ -50,8 +50,9 @@ public class TokenCacheUtils { } Long time = expTime * 60; - CacheUtils.put(KEY, token, userId, time.intValue(), null); - + Double v = time * 0.6; + CacheUtils.put(KEY, token, userId, time.intValue(), v.intValue()); + CacheUtils.flush(KEY); } public static void remove(String token) { diff --git a/backend/src/main/java/io/dataease/listener/ApplicationCloseEventListener.java b/backend/src/main/java/io/dataease/listener/ApplicationCloseEventListener.java new file mode 100644 index 0000000000..4fde79311f --- /dev/null +++ b/backend/src/main/java/io/dataease/listener/ApplicationCloseEventListener.java @@ -0,0 +1,24 @@ +package io.dataease.listener; + +import io.dataease.commons.utils.LogUtil; +import net.sf.ehcache.CacheManager; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationCloseEventListener implements ApplicationListener { + + @Autowired(required = false) + CacheManager cacheManager; + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + + if (ObjectUtils.isNotEmpty(cacheManager)) + cacheManager.shutdown(); + LogUtil.info("DataEase is stopping"); + } +} diff --git a/backend/src/main/java/io/dataease/listener/util/CacheUtils.java b/backend/src/main/java/io/dataease/listener/util/CacheUtils.java index 22ebed424a..9a4fd3aa88 100644 --- a/backend/src/main/java/io/dataease/listener/util/CacheUtils.java +++ b/backend/src/main/java/io/dataease/listener/util/CacheUtils.java @@ -66,6 +66,12 @@ public class CacheUtils { return cache(cacheName).remove(key); } + public static void flush(String cacheName) { + CacheManager manager = getCacheManager(); + if (manager instanceof RedisCacheManager) return; + cache(cacheName).flush(); + } + public static void removeAll(String cacheName) { if (getCacheManager() instanceof RedisCacheManager) { org.springframework.cache.Cache cache = getCacheManager().getCache(cacheName); diff --git a/backend/src/main/resources/ehcache/ehcache.xml b/backend/src/main/resources/ehcache/ehcache.xml index 5976911a62..144cdb7ad6 100644 --- a/backend/src/main/resources/ehcache/ehcache.xml +++ b/backend/src/main/resources/ehcache/ehcache.xml @@ -272,12 +272,16 @@ + timeToIdleSeconds="28800" + timeToLiveSeconds="28800" + memoryStoreEvictionPolicy="LRU" + diskPersistent="true"> + + diff --git a/frontend/package.json b/frontend/package.json index 3816f0a7ab..1a2b29da57 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -88,7 +88,8 @@ "vuedraggable": "^2.24.3", "vuex": "3.1.0", "webpack": "^4.46.0", - "xlsx": "^0.17.0" + "xlsx": "^0.17.0", + "xss": "^1.0.14" }, "devDependencies": { "@babel/core": "^7.4.0-0", diff --git a/frontend/src/components/canvas/customComponent/DeRichText.vue b/frontend/src/components/canvas/customComponent/DeRichText.vue index 4e5a76ab38..195a4bc263 100644 --- a/frontend/src/components/canvas/customComponent/DeRichText.vue +++ b/frontend/src/components/canvas/customComponent/DeRichText.vue @@ -37,6 +37,7 @@ import 'tinymce/plugins/nonbreaking' import 'tinymce/plugins/pagebreak' import { mapState } from 'vuex' import Vue from 'vue' +import xssCheck from 'xss' export default { name: 'DeRichText', @@ -77,7 +78,7 @@ export default { canEdit: false, // 初始化配置 tinymceId: 'tinymce-' + this.element.id, - myValue: this.propValue, + myValue: xssCheck(this.propValue), init: { selector: '#tinymce-' + this.element.id, toolbar_items_size: 'small', diff --git a/frontend/src/components/canvas/customComponent/DeRichTextView.vue b/frontend/src/components/canvas/customComponent/DeRichTextView.vue index 7b5f717876..fe66736ee2 100644 --- a/frontend/src/components/canvas/customComponent/DeRichTextView.vue +++ b/frontend/src/components/canvas/customComponent/DeRichTextView.vue @@ -38,6 +38,7 @@ import 'tinymce/plugins/pagebreak' import { mapState } from 'vuex' import bus from '@/utils/bus' import { uuid } from 'vue-uuid' +import xssCheck from 'xss' export default { name: 'DeRichTextView', @@ -152,7 +153,7 @@ export default { viewInit() { bus.$on('fieldSelect-' + this.element.propValue.viewId, this.fieldSelect) tinymce.init({}) - this.myValue = this.assignment(this.element.propValue.textValue) + this.myValue = xssCheck(this.assignment(this.element.propValue.textValue)) bus.$on('initCurFields-' + this.element.id, this.initCurFieldsChange) this.$nextTick(() => { this.initReady = true diff --git a/frontend/src/components/canvas/customComponent/UserView.vue b/frontend/src/components/canvas/customComponent/UserView.vue index da4713b75f..e5de90d083 100644 --- a/frontend/src/components/canvas/customComponent/UserView.vue +++ b/frontend/src/components/canvas/customComponent/UserView.vue @@ -759,7 +759,7 @@ export default { const attrSize = JSON.parse(this.view.customAttr).size if (this.chart.type === 'table-info' && this.view.datasetMode === 0 && (!attrSize.tablePageMode || attrSize.tablePageMode === 'page')) { requestInfo.goPage = this.currentPage.page - requestInfo.pageSize = this.currentPage.pageSize + requestInfo.pageSize = this.currentPage.pageSize === parseInt(attrSize.tablePageSize) ? this.currentPage.pageSize : parseInt(attrSize.tablePageSize) } } if (this.isFirstLoad) { diff --git a/frontend/src/components/canvas/customComponent/UserViewDialog.vue b/frontend/src/components/canvas/customComponent/UserViewDialog.vue index a3a4d023ce..58a4e5fee1 100644 --- a/frontend/src/components/canvas/customComponent/UserViewDialog.vue +++ b/frontend/src/components/canvas/customComponent/UserViewDialog.vue @@ -64,7 +64,7 @@ :enable-scroll="false" :chart="chartTable" :show-summary="false" - class="table-class" + class="table-class-dialog" /> @@ -342,8 +342,9 @@ export default { height: 100%; } -.table-class { +.table-class-dialog { height: 100%; + overflow-y: auto !important; } .canvas-class { diff --git a/frontend/src/components/canvas/customComponent/VText.vue b/frontend/src/components/canvas/customComponent/VText.vue index f23c2ca559..8dbd863c42 100644 --- a/frontend/src/components/canvas/customComponent/VText.vue +++ b/frontend/src/components/canvas/customComponent/VText.vue @@ -18,7 +18,7 @@ @mousedown="handleMousedown" @blur="handleBlur" @input="handleInput" - v-html="element.propValue" + v-html="$xss(element.propValue)" />
@@ -80,7 +80,7 @@ export default { }, textInfo() { if (this.element && this.element.hyperlinks && this.element.hyperlinks.enable) { - return "" + this.element.propValue + '' + return '' + this.element.propValue + '' } else { return this.element.propValue } diff --git a/frontend/src/components/widget/deWidget/DeTabs.vue b/frontend/src/components/widget/deWidget/DeTabs.vue index 3e9fe23856..786622fa4e 100644 --- a/frontend/src/components/widget/deWidget/DeTabs.vue +++ b/frontend/src/components/widget/deWidget/DeTabs.vue @@ -411,6 +411,10 @@ export default { const _this = this _this.$nextTick(() => { try { + const targetRef = _this.$refs['canvasTabRef-' + _this.activeTabName] + if (targetRef) { + targetRef[0].restore() + } _this.$refs[this.activeTabName][0].resizeChart() } catch (e) { // ignore diff --git a/frontend/src/main.js b/frontend/src/main.js index 459f66280a..673cf928ec 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -43,6 +43,12 @@ import 'video.js/dist/video-js.css' // 控制标签宽高成比例的指令 import proportion from 'vue-proportion-directive' +import xss from 'xss' +// 定义全局XSS解决方法 +Object.defineProperty(Vue.prototype, '$xss', { + value: xss +}) + Vue.config.productionTip = false Vue.use(VueClipboard) Vue.use(widgets) diff --git a/frontend/src/permission.js b/frontend/src/permission.js index afe2e5421e..efcede886b 100644 --- a/frontend/src/permission.js +++ b/frontend/src/permission.js @@ -57,7 +57,7 @@ const routeBefore = (callBack) => { callBack() } } -router.beforeEach(async(to, from, next) => routeBefore(() => { +router.beforeEach(async (to, from, next) => routeBefore(() => { // start progress bar NProgress.start() const mobileIgnores = ['/delink', '/de-auto-login'] @@ -69,6 +69,9 @@ router.beforeEach(async(to, from, next) => routeBefore(() => { if (hasToken) { urlSuffix += ('?detoken=' + hasToken) } + localStorage.removeItem('user-info') + localStorage.removeItem('userId') + localStorage.removeItem('Authorization') window.location.href = window.origin + urlSuffix NProgress.done() }