forked from github/dataease
feat(登录): 多客户端同时登录限制
This commit is contained in:
parent
8b3fe64325
commit
1c24f8dba3
@ -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();
|
||||
|
@ -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;
|
||||
}
|
@ -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())) {
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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');
|
@ -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>
|
@ -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',
|
||||
|
@ -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!`'
|
||||
}
|
||||
}
|
||||
|
@ -2863,5 +2863,14 @@ export default {
|
||||
reset: '重置',
|
||||
preview: '預覽',
|
||||
save: '保存'
|
||||
},
|
||||
multi_login_lang: {
|
||||
title: '當前賬號已在線!',
|
||||
ip: 'IP',
|
||||
time: '登錄時間',
|
||||
label: '禁止多端登錄!',
|
||||
confirm_title: '強行登錄會導致其他客戶端掉線',
|
||||
confirm: '是否強行登錄?',
|
||||
forced_offline: '`當前賬號在客戶端【${ip}】登錄,您已被擠下線!`'
|
||||
}
|
||||
}
|
||||
|
@ -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}】登录,您已被挤下线!`'
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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>'
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 => {
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user