feat(登录): 多客户端同时登录限制

This commit is contained in:
fit2cloud-chenyw 2023-03-13 14:27:01 +08:00
parent 8b3fe64325
commit 1c24f8dba3
23 changed files with 418 additions and 26 deletions

View File

@ -3,6 +3,7 @@ package io.dataease.auth.api;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.dataease.auth.api.dto.SeizeLoginDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
@ -21,6 +22,9 @@ public interface AuthApi {
@PostMapping("/login")
Object login(LoginDto loginDto) throws Exception;
@PostMapping("/seizeLogin")
Object seizeLogin(SeizeLoginDto loginDto) throws Exception;
@ApiOperation("获取用户信息")
@PostMapping("/userInfo")
CurrentUserDto userInfo();

View File

@ -0,0 +1,13 @@
package io.dataease.auth.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class SeizeLoginDto implements Serializable {
private static final long serialVersionUID = -3318473577764636483L;
private String token;
}

View File

@ -4,6 +4,7 @@ import io.dataease.auth.api.AuthApi;
import io.dataease.auth.api.dto.CurrentRoleDto;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.dataease.auth.api.dto.SeizeLoginDto;
import io.dataease.auth.config.RsaProperties;
import io.dataease.auth.entity.AccountLockStatus;
import io.dataease.auth.entity.SysUserEntity;
@ -28,6 +29,8 @@ 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;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
@ -61,6 +64,9 @@ public class AuthServer implements AuthApi {
@Resource
private SystemParameterService systemParameterService;
@Autowired
private WsService wsService;
@Override
public Object login(@RequestBody LoginDto loginDto) throws Exception {
Map<String, Object> result = new HashMap<>();
@ -164,6 +170,23 @@ public class AuthServer implements AuthApi {
return result;
}
@Override
public Object seizeLogin(@RequestBody SeizeLoginDto loginDto) throws Exception {
String token = loginDto.getToken();
Map<String, Object> result = new HashMap<>();
result.put("token", token);
ServletUtils.setToken(token);
TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token);
Long userId = tokenInfo.getUserId();
JWTUtils.seizeSign(userId, token);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, userId, null, null, null);
WsMessage message = new WsMessage(userId, "/web-seize-topic", IPUtils.get());
wsService.releaseMessage(message);
authUserService.clearCache(userId);
Thread.sleep(3000L);
return result;
}
private String appendLoginErrorMsg(String msg, AccountLockStatus lockStatus) {
if (ObjectUtils.isEmpty(lockStatus)) return msg;
if (ObjectUtils.isNotEmpty(lockStatus.getRemainderTimes())) {

View File

@ -81,6 +81,7 @@ public class ShiroServiceImpl implements ShiroService {
filterChainDefinitionMap.put("/api/auth/login", ANON);
filterChainDefinitionMap.put("/api/auth/seizeLogin", ANON);
filterChainDefinitionMap.put("/api/auth/logout", ANON);
filterChainDefinitionMap.put("/api/auth/isPluginLoaded", ANON);
filterChainDefinitionMap.put("/system/requestTimeOut", ANON);

View File

@ -6,15 +6,24 @@ import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import com.google.gson.Gson;
import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.entity.TokenInfo.TokenInfoBuilder;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.exception.DEException;
import io.dataease.commons.model.OnlineUserModel;
import io.dataease.commons.utils.*;
import io.dataease.exception.DataEaseException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import java.util.Date;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JWTUtils {
@ -68,23 +77,105 @@ public class JWTUtils {
* @return 加密的token
*/
public static String sign(TokenInfo tokenInfo, String secret) {
return sign(tokenInfo, secret, true);
}
private static boolean tokenValid(OnlineUserModel model) {
String token = model.getToken();
// 如果已经加入黑名单 则直接返回无效
boolean invalid = TokenCacheUtils.invalid(token);
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;
// 如果当前时间减去登录时间小于超时时间则说明token未过期 返回有效状态
return System.currentTimeMillis() - loginTime < expireTimeMillis;
}
private static Set<OnlineUserModel> filterValid(Set<OnlineUserModel> userModels) {
Set<OnlineUserModel> models = userModels.stream().filter(JWTUtils::tokenValid).collect(Collectors.toSet());
return models;
}
private static String models2Json(Set<OnlineUserModel> models, boolean withCurToken, String token) {
Gson gson = new Gson();
List<OnlineUserModel> userModels = models.stream().map(item -> {
item.setToken(null);
return item;
}).collect(Collectors.toList());
if (withCurToken) {
userModels.get(0).setToken(token);
}
String json = gson.toJson(userModels);
try {
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;
return URLEncoder.encode(json, "utf-8");
} catch (Exception e) {
return null;
}
}
public static String seizeSign(Long userId, String token) {
Set<OnlineUserModel> userModels = Optional.ofNullable(TokenCacheUtils.onlineUserTokens(userId)).orElse(new LinkedHashSet<>());
userModels.stream().forEach(model -> {
TokenCacheUtils.add(model.getToken(), userId);
});
userModels.clear();
OnlineUserModel curModel = TokenCacheUtils.buildModel(token);
userModels.add(curModel);
TokenCacheUtils.resetOnlinePools(userId, userModels);
return IPUtils.get();
}
public static String sign(TokenInfo tokenInfo, String secret, boolean writeOnline) {
Long userId = tokenInfo.getUserId();
String multiLoginType = null;
if (writeOnline && StringUtils.equals("1", (multiLoginType = TokenCacheUtils.multiLoginType()))) {
Set<OnlineUserModel> userModels = TokenCacheUtils.onlineUserTokens(userId);
if (CollectionUtils.isNotEmpty(userModels) && CollectionUtils.isNotEmpty((userModels = filterValid(userModels)))) {
TokenCacheUtils.resetOnlinePools(userId, userModels);
HttpServletResponse response = ServletUtils.response();
Cookie cookie_token = new Cookie("MultiLoginError1", models2Json(userModels, false, null));cookie_token.setPath("/");
cookie_token.setPath("/");
response.addCookie(cookie_token);
DataEaseException.throwException("MultiLoginError1");
}
}
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", userId);
String sign = builder.withExpiresAt(date).sign(algorithm);
if (writeOnline && !StringUtils.equals("0", multiLoginType)) {
if (StringUtils.equals("2", multiLoginType)) {
Set<OnlineUserModel> userModels = TokenCacheUtils.onlineUserTokens(userId);
if (CollectionUtils.isNotEmpty(userModels) && CollectionUtils.isNotEmpty((userModels = filterValid(userModels)))) {
HttpServletResponse response = ServletUtils.response();
Cookie cookie_token = new Cookie("MultiLoginError2", models2Json(userModels, true, sign));
cookie_token.setPath("/");
response.addCookie(cookie_token);
userModels = userModels.stream().filter(mode -> !StringUtils.equals(mode.getToken(), sign)).collect(Collectors.toSet());
TokenCacheUtils.resetOnlinePools(userId, userModels);
DataEaseException.throwException("MultiLoginError");
}
}
TokenCacheUtils.add2OnlinePools(sign, userId);
}
return sign;
}
public static String signLink(String resourceId, Long userId, String secret) {
Algorithm algorithm = Algorithm.HMAC256(secret);
if (userId == null) {

View File

@ -127,6 +127,8 @@ public interface ParamConstants {
LOGIN_LIMIT_OPEN("loginlimit.open"),
SCAN_CREATE_USER("loginlimit.scanCreateUser"),
MULTI_LOGIN("loginlimit.multiLogin"),
TEMPLATE_ACCESS_KEY("basic.templateAccessKey");
private String value;

View File

@ -0,0 +1,19 @@
package io.dataease.commons.model;
import lombok.Data;
import net.minidev.json.annotate.JsonIgnore;
import java.io.Serializable;
@Data
public class OnlineUserModel implements Serializable {
private static final long serialVersionUID = 190044376129186283L;
@JsonIgnore
private String token;
private String ip;
private Long loginTime;
}

View File

@ -1,13 +1,18 @@
package io.dataease.commons.utils;
import io.dataease.commons.model.OnlineUserModel;
import io.dataease.listener.util.CacheUtils;
import io.dataease.service.system.SystemParameterService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@ -17,6 +22,8 @@ public class TokenCacheUtils {
private static final String KEY = "sys_token_store";
private static final String ONLINE_TOKEN_POOL_KEY = "online_token_store";
private static String cacheType;
private static Long expTime;
@ -76,4 +83,63 @@ public class TokenCacheUtils {
return ObjectUtils.isNotEmpty(sys_token_store) && StringUtils.isNotBlank(sys_token_store.toString());
}
public static void resetOnlinePools(Long userId, Set<OnlineUserModel> sets) {
if (useRedis()) {
RedisTemplate redisTemplate = (RedisTemplate) CommonBeanFactory.getBean("redisTemplate");
redisTemplate.delete(ONLINE_TOKEN_POOL_KEY + userId);
SetOperations setOperations = redisTemplate.opsForSet();
Object[] modelArray = sets.stream().toArray();
setOperations.add(ONLINE_TOKEN_POOL_KEY + userId, modelArray);
return;
}
CacheUtils.removeAll(ONLINE_TOKEN_POOL_KEY);
CacheUtils.put(ONLINE_TOKEN_POOL_KEY, userId, sets, null, null);
CacheUtils.flush(ONLINE_TOKEN_POOL_KEY);
}
public static void add2OnlinePools(String token, Long userId) {
if (useRedis()) {
RedisTemplate redisTemplate = (RedisTemplate) CommonBeanFactory.getBean("redisTemplate");
SetOperations setOperations = redisTemplate.opsForSet();
setOperations.add(ONLINE_TOKEN_POOL_KEY + userId, buildModel(token));
return;
}
Object listObj = null;
Set<OnlineUserModel> models = null;
if (ObjectUtils.isEmpty(listObj = CacheUtils.get(ONLINE_TOKEN_POOL_KEY, userId))) {
models = new LinkedHashSet<>();
} else {
models = (Set<OnlineUserModel>) listObj;
}
models.add(buildModel(token));
CacheUtils.put(ONLINE_TOKEN_POOL_KEY, userId, models, null, null);
CacheUtils.flush(ONLINE_TOKEN_POOL_KEY);
}
public static String multiLoginType() {
SystemParameterService service = CommonBeanFactory.getBean(SystemParameterService.class);
return service.multiLoginType();
}
public static Set<OnlineUserModel> onlineUserTokens(Long userId) {
if (useRedis()) {
RedisTemplate redisTemplate = (RedisTemplate) CommonBeanFactory.getBean("redisTemplate");
SetOperations setOperations = redisTemplate.opsForSet();
Set tokens = setOperations.members(ONLINE_TOKEN_POOL_KEY + userId);
return tokens;
}
Object o = CacheUtils.get(ONLINE_TOKEN_POOL_KEY, userId);
if (ObjectUtils.isNotEmpty(o))
return (Set<OnlineUserModel>) o;
return null;
}
public static OnlineUserModel buildModel(String token) {
OnlineUserModel model = new OnlineUserModel();
model.setToken(token);
model.setIp(IPUtils.get());
model.setLoginTime(System.currentTimeMillis());
return model;
}
}

View File

@ -6,13 +6,10 @@ import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.service.impl.AuthUserServiceImpl;
import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.utils.*;
import io.dataease.dto.PermissionProxy;
import io.dataease.dto.chart.ViewOption;
import io.dataease.ext.ExtTaskMapper;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.utils.CronUtils;
import io.dataease.commons.utils.LogUtil;
import io.dataease.commons.utils.ServletUtils;
import io.dataease.job.sechedule.ScheduleManager;
import io.dataease.job.sechedule.strategy.TaskHandler;
import io.dataease.plugins.common.base.domain.SysUserAssist;
@ -164,6 +161,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
AuthUserServiceImpl userService = SpringContextUtil.getBean(AuthUserServiceImpl.class);
SysUserService sysUserService = SpringContextUtil.getBean(SysUserService.class);
List<File> files = null;
String token = null;
try {
XpackEmailTemplateDTO emailTemplateDTO = emailXpackService.emailTemplate(taskInstance.getTaskId());
XpackEmailTaskRequest taskForm = emailXpackService.taskForm(taskInstance.getTaskId());
@ -173,7 +171,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
}
String panelId = emailTemplateDTO.getPanelId();
String url = panelUrl(panelId);
String token = tokenByUser(user);
token = tokenByUser(user);
XpackPixelEntity xpackPixelEntity = buildPixel(emailTemplateDTO);
LogUtil.info("url is " + url);
LogUtil.info("token is " + token);
@ -349,6 +347,9 @@ public class EmailTaskHandler extends TaskHandler implements Job {
error(taskInstance, e);
LogUtil.error(e.getMessage(), e);
} finally {
if (StringUtils.isNotBlank(token)) {
TokenCacheUtils.add(token, user.getUserId());
}
if (CollectionUtils.isNotEmpty(files)) {
files.forEach(file -> {
if (file.exists()) {
@ -381,7 +382,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
private String tokenByUser(SysUserEntity user) {
TokenInfo tokenInfo = TokenInfo.builder().userId(user.getUserId()).username(user.getUsername()).build();
String token = JWTUtils.sign(tokenInfo, user.getPassword());
String token = JWTUtils.sign(tokenInfo, user.getPassword(), false);
return token;
}

View File

@ -21,6 +21,7 @@ import io.dataease.service.datasource.DatasourceService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -34,6 +35,7 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import io.dataease.ext.*;
import springfox.documentation.annotations.Cacheable;
@Service
@Transactional(rollbackFor = Exception.class)
@ -125,6 +127,13 @@ public class SystemParameterService {
boolean open = StringUtils.equals("true", param.getParamValue());
result.setScanCreateUser(open ? "true" : "false");
}
if (StringUtils.equals(param.getParamKey(), ParamConstants.BASIC.MULTI_LOGIN.getValue())) {
String paramValue = param.getParamValue();
result.setMultiLogin("0");
if (StringUtils.isNotBlank(paramValue)) {
result.setMultiLogin(paramValue);
}
}
}
}
@ -150,6 +159,7 @@ public class SystemParameterService {
public CasSaveResult editBasic(List<SystemParameter> parameters) {
CasSaveResult casSaveResult = afterSwitchDefaultLogin(parameters);
BasicInfo basicInfo = basicInfo();
String oldMultiLogin = this.getValue("loginlimit.multiLogin");
for (int i = 0; i < parameters.size(); i++) {
SystemParameter parameter = parameters.get(i);
SystemParameterExample example = new SystemParameterExample();
@ -163,6 +173,10 @@ public class SystemParameterService {
example.clear();
}
datasourceService.updateDatasourceStatusJob(basicInfo, parameters);
String newMultiLogin = this.getValue("loginlimit.multiLogin");
if (!StringUtils.equals(oldMultiLogin, newMultiLogin)) {
clearMultiLoginCache();
}
return casSaveResult;
}
@ -364,4 +378,17 @@ public class SystemParameterService {
return basicInfo;
}
@Cacheable(value = "multiLogin")
public String multiLoginType() {
String value = getValue("loginlimit.multiLogin");
if (StringUtils.isBlank(value)) {
value = "0";
}
return value;
}
@CacheEvict("multiLogin")
public void clearMultiLoginCache() {
}
}

View File

@ -52,4 +52,6 @@ END if;
END
;;
delimiter ;
delimiter ;
INSERT INTO `system_parameter` (`param_key`, `param_value`, `type`, `sort`) VALUES ('loginlimit.multiLogin', '0', 'text', '3');

View File

@ -283,7 +283,30 @@
<BootstrapCacheLoaderFactory class="net.sf.ehcache.store.DiskStoreBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=true" />
</cache>
<cache
name="online_token_store"
eternal="false"
maxElementsInMemory="5000"
maxElementsOnDisk="50000"
overflowToDisk="true"
timeToIdleSeconds="28800"
timeToLiveSeconds="28800"
memoryStoreEvictionPolicy="LRU"
diskPersistent="true">
<BootstrapCacheLoaderFactory class="net.sf.ehcache.store.DiskStoreBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=true" />
</cache>
<cache
name="multiLogin"
eternal="false"
maxElementsInMemory="100"
maxElementsOnDisk="1000"
overflowToDisk="true"
diskPersistent="true"
timeToIdleSeconds="1800"
timeToLiveSeconds="3600"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

View File

@ -8,6 +8,15 @@ export function login(data) {
})
}
export function seizeLogin(data) {
return request({
url: '/api/auth/seizeLogin',
method: 'post',
data,
loading: true
})
}
export function getInfo(token) {
return request({
url: '/api/auth/userInfo',

View File

@ -2870,5 +2870,14 @@ export default {
reset: 'Reset',
preview: 'Preview',
save: 'Save'
},
multi_login_lang: {
title: 'The current account is online!',
ip: 'IP',
time: 'Login time',
label: 'Prohibit multi-terminal login!',
confirm_title: 'Forced login will cause other clients to go offline',
confirm: 'Whether to force login?',
forced_offline: '`The current account is logged in on the client [${ip}],and you have been pushed off the line!`'
}
}

View File

@ -2863,5 +2863,14 @@ export default {
reset: '重置',
preview: '預覽',
save: '保存'
},
multi_login_lang: {
title: '當前賬號已在線!',
ip: 'IP',
time: '登錄時間',
label: '禁止多端登錄!',
confirm_title: '強行登錄會導致其他客戶端掉線',
confirm: '是否強行登錄?',
forced_offline: '`當前賬號在客戶端【${ip}】登錄,您已被擠下線!`'
}
}

View File

@ -1,3 +1,5 @@
import {$confirm} from "@/utils/message";
export default {
fu: {
search_bar: {
@ -2863,5 +2865,14 @@ export default {
reset: '重置',
preview: '预览',
save: '保存'
},
multi_login_lang: {
title: '当前账号已在线!',
ip: 'IP',
time: '登录时间',
label: '禁止多端登录!',
confirm_title: '强行登录会导致其他客户端掉线',
confirm: '是否强行登录?',
forced_offline: '`当前账号在客户端【${ip}】登录,您已被挤下线!`'
}
}

View File

@ -61,6 +61,7 @@ import DeMainContainer from '@/components/dataease/DeMainContainer'
import DeContainer from '@/components/dataease/DeContainer'
import DeAsideContainer from '@/components/dataease/DeAsideContainer'
import bus from '@/utils/bus'
import { showMultiLoginMsg } from '@/utils/index'
import { needModifyPwd, removePwdTips } from '@/api/user'
@ -131,11 +132,22 @@ export default {
},
mounted() {
bus.$on('PanelSwitchComponent', this.panelSwitchComponent)
bus.$on('web-seize-topic-call', this.webMsgTopicCall)
},
beforeDestroy() {
bus.$off('PanelSwitchComponent', this.panelSwitchComponent)
bus.$off('web-seize-topic-call', this.webMsgTopicCall)
},
created() {
showMultiLoginMsg()
},
methods: {
webMsgTopicCall(param) {
const ip = param
const msg = this.$t('multi_login_lang.forced_offline')
this.$error(eval(msg))
bus.$emit('sys-logout')
},
panelSwitchComponent(c) {
this.componentName = c.name
},

View File

@ -1,4 +1,10 @@
import Cookies from 'js-cookie'
import i18n from '@/lang'
import { $error, $confirm } from '@/utils/message'
import {seizeLogin} from '@/api/user'
import router from '@/router'
import store from "@/store";
import { Loading } from 'element-ui';
export function timeSection(date, type, labelFormat = 'yyyy-MM-dd') {
if (!date) {
return null
@ -352,3 +358,45 @@ export const inOtherPlatform = () => {
}
return false
}
export const showMultiLoginMsg = () => {
const multiLoginError1 = Cookies.get('MultiLoginError1')
if (multiLoginError1) {
Cookies.remove('MultiLoginError1')
const infos = JSON.parse(multiLoginError1)
const content = infos.map(info => buildMultiLoginErrorItem(info)).join('</br>')
let msgContent = '<strong>' + i18n.t('multi_login_lang.title') + '</strong>'
msgContent += content + '<p>' + i18n.t('multi_login_lang.label') + '</p>'
$error(msgContent, 10000, true);
}
const multiLoginError2 = Cookies.get('MultiLoginError2')
if (multiLoginError2) {
const infos = JSON.parse(multiLoginError2)
Cookies.remove('MultiLoginError2')
const content = infos.map(info => buildMultiLoginErrorItem(info)).join('</br>')
let msgContent = '<strong>' + i18n.t('multi_login_lang.confirm_title') + '</strong>'
msgContent += content + '<p>' + i18n.t('multi_login_lang.confirm') + '</p>'
$confirm(msgContent, () => seize(infos[0]), {
dangerouslyUseHTMLString: true
});
}
}
const seize = model => {
let loadingInstance = Loading.service({});
const token = model.token
const param = {
token
}
seizeLogin(param).then(res => {
const resultToken = res.data.token
store.dispatch('user/refreshToken', resultToken)
router.push('/')
loadingInstance.close();
})
}
const buildMultiLoginErrorItem = (info) => {
if (!info) return null
const ip = i18n.t('multi_login_lang.ip')
const time = i18n.t('multi_login_lang.time')
return '<p>' + ip + ': ' + info.ip + ', ' + time + ': ' + new Date(info.loginTime).format('yyyy-MM-dd hh:mm:ss') + '</p>'
}

View File

@ -47,12 +47,13 @@ export const $warning = (message, duration) => {
})
}
export const $error = (message, duration) => {
export const $error = (message, duration, useHtml) => {
Message.error({
message: message,
type: 'error',
showClose: true,
duration: duration || 10000
duration: duration || 10000,
dangerouslyUseHTMLString: useHtml
})
}

View File

@ -118,7 +118,7 @@ service.interceptors.response.use(response => {
if (msg.length > 600) {
msg = msg.slice(0, 600)
}
!config.hideMsg && (!headers['authentication-status']) && $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 => {

View File

@ -212,7 +212,7 @@
import { encrypt } from '@/utils/rsaEncrypt'
import { ldapStatus, oidcStatus, getPublicKey, pluginLoaded, defaultLoginType, wecomStatus, dingtalkStatus, larkStatus, larksuiteStatus, casStatus, casLoginPage } from '@/api/user'
import { getSysUI } from '@/utils/auth'
import { changeFavicon } from '@/utils/index'
import { changeFavicon, showMultiLoginMsg } from '@/utils/index'
import { initTheme } from '@/utils/ThemeUtil'
import PluginCom from '@/views/system/plugin/PluginCom'
import Cookies from 'js-cookie'
@ -395,6 +395,7 @@ export default {
this.$error(Cookies.get('LarksuiteError'))
}
this.clearLarksuiteMsg()
showMultiLoginMsg()
},
methods: {
@ -476,14 +477,18 @@ export default {
this.$store.dispatch('user/login', user).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
}).catch((e) => {
this.loading = false
e?.response?.data?.message?.startsWith('MultiLoginError') && this.showMessage()
})
} else {
return false
}
})
},
showMessage() {
showMultiLoginMsg()
},
changeLoginType(val) {
if (val !== 2 && val !== 7) return
this.clearOidcMsg()

View File

@ -159,6 +159,13 @@
component-name="LoginLimitSetting"
/>
<plugin-com
v-if="isPluginLoaded"
ref="MultiLoginLimit"
:form="formInline"
component-name="MultiLoginLimit"
/>
<plugin-com
v-if="isPluginLoaded && scanOpen"
ref="ScanLimitSetting"
@ -429,6 +436,12 @@ export default {
paramValue: this.formInline.scanCreateUser,
type: 'text',
sort: 3
},
{
paramKey: 'loginlimit.multiLogin',
paramValue: this.formInline.multiLogin,
type: 'text',
sort: 3
}
]

View File

@ -2,7 +2,6 @@ import bus from '@/utils/bus'
import SockJS from 'sockjs-client'
import Stomp from 'stompjs'
import store from '@/store'
class DeWebsocket {
constructor() {
this.ws_url = '/websocket'
@ -11,6 +10,10 @@ class DeWebsocket {
{
topic: '/web-msg-topic',
event: 'web-msg-topic-call'
},
{
topic: '/web-seize-topic',
event: 'web-seize-topic-call'
}
]
this.timer = null