diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java index 239c1337..7db8c218 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -17,6 +17,7 @@ package cn.dev33.satoken.config; import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode; import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange; +import cn.dev33.satoken.stp.parameter.enums.SaRepeatLoginsMode; import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange; import cn.dev33.satoken.util.SaFoxUtil; @@ -64,6 +65,11 @@ public class SaTokenConfig implements Serializable { */ private Boolean isShare = false; + /** + * 当 isConcurrent=false 时,多客户端登录时的处理策略 + */ + private SaRepeatLoginsMode repeatLoginsMode = SaRepeatLoginsMode.KICKOUT; + /** * 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端) */ @@ -713,6 +719,22 @@ public class SaTokenConfig implements Serializable { return this; } + /** + * @return 不允许并发登录时,重复登录处理策略 + */ + public SaRepeatLoginsMode getRepeatLoginsMode() { + return repeatLoginsMode; + } + + /** + * @param repeatLoginsMode 不允许并发登录时,重复登录处理策略 + * @return 对象自身 + */ + public SaTokenConfig setRepeatLoginsMode(SaRepeatLoginsMode repeatLoginsMode) { + this.repeatLoginsMode = repeatLoginsMode; + return this; + } + /** * 获取 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端) * @@ -860,6 +882,7 @@ public class SaTokenConfig implements Serializable { + ", isConcurrent=" + isConcurrent + ", isShare=" + isShare + ", replacedRange=" + replacedRange + + ", repeatLoginsMode=" + repeatLoginsMode + ", maxLoginCount=" + maxLoginCount + ", overflowLogoutMode=" + overflowLogoutMode + ", maxTryTimes=" + maxTryTimes diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/error/SaErrorCode.java b/sa-token-core/src/main/java/cn/dev33/satoken/error/SaErrorCode.java index bd5e329a..c6cd3e93 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/error/SaErrorCode.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/error/SaErrorCode.java @@ -80,6 +80,9 @@ public interface SaErrorCode { /** 更改 Token 指向的 账号Id 时,账号Id值为空 */ int CODE_11003 = 11003; + /** 当前账号已经登录 */ + int CODE_11004 = 11004; + /** 未能读取到有效Token */ int CODE_11011 = 11011; diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 539b4497..362dea08 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -36,6 +36,7 @@ import cn.dev33.satoken.stp.parameter.SaLoginParameter; import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode; import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange; +import cn.dev33.satoken.stp.parameter.enums.SaRepeatLoginsMode; import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; @@ -538,13 +539,27 @@ public class StpLogic { protected String distUsableToken(Object id, SaLoginParameter loginParameter) { // 1、获取全局配置的 isConcurrent 参数 - // 如果配置为:不允许一个账号多地同时登录,则需要先将这个账号的历史登录会话标记为:被顶下线 + // 如果配置为:不允许一个账号多地同时登录,则需要根据配置选择: + // 一.将这个账号的历史登录会话标记为:被顶下线 + // 二.提示错误并拒绝本次登录 if( ! loginParameter.getIsConcurrent()) { - if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) { - replaced(id, loginParameter.getDeviceType()); - } - if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) { - replaced(id, createSaLogoutParameter()); + if (loginParameter.getRepeatLoginsMode() == SaRepeatLoginsMode.KICKOUT){ + if(loginParameter.getReplacedRange() == SaReplacedRange.CURR_DEVICE_TYPE) { + replaced(id, loginParameter.getDeviceType()); + } + if(loginParameter.getReplacedRange() == SaReplacedRange.ALL_DEVICE_TYPE) { + replaced(id, createSaLogoutParameter()); + } + } else if (loginParameter.getRepeatLoginsMode() == SaRepeatLoginsMode.INTERCEPT){ + List terminalListByLoginId = getTerminalListByLoginId(id); + // 只有当存在有效的会话时才拒绝登录 + boolean hasActiveSession = terminalListByLoginId.stream() + .anyMatch(terminal -> isValidToken(terminal.getTokenValue())); + + if (hasActiveSession) { + throw new SaTokenException("当前账号已在其他客户端登录").setCode(SaErrorCode.CODE_11004); + } + } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/SaLoginParameter.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/SaLoginParameter.java index ee3c4329..9ff21882 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/SaLoginParameter.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/SaLoginParameter.java @@ -21,6 +21,7 @@ import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.fun.SaParamFunction; import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode; +import cn.dev33.satoken.stp.parameter.enums.SaRepeatLoginsMode; import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange; import cn.dev33.satoken.util.SaTokenConsts; @@ -110,6 +111,11 @@ public class SaLoginParameter { */ private Boolean isWriteHeader; + /** + * 当 isConcurrent=false 时,多客户端登录时的处理策略 + */ + private SaRepeatLoginsMode repeatLoginsMode; + /** * 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端) */ @@ -158,6 +164,7 @@ public class SaLoginParameter { this.replacedRange = config.getReplacedRange(); this.overflowLogoutMode = config.getOverflowLogoutMode(); this.rightNowCreateTokenSession = config.getRightNowCreateTokenSession(); + this.repeatLoginsMode = config.getRepeatLoginsMode(); this.setupCookieConfig(cookie -> { SaCookieConfig gCookie = config.getCookie(); @@ -568,6 +575,23 @@ public class SaLoginParameter { return this; } + + /** + * 获取:多客户端登录时的处理策略 + * @return 多客户端登录时的处理策略 + */ + public SaRepeatLoginsMode getRepeatLoginsMode() { + return repeatLoginsMode; + } + + /** + * 设置:多客户端登录时的处理策略 + * @param repeatLoginsMode 多客户端登录时的处理策略 + */ + public void setRepeatLoginsMode(SaRepeatLoginsMode repeatLoginsMode) { + this.repeatLoginsMode = repeatLoginsMode; + } + /* * toString */ @@ -577,6 +601,7 @@ public class SaLoginParameter { + "deviceType=" + deviceType + ", deviceId=" + deviceId + ", replacedRange=" + replacedRange + + ", repeatLoginsMode=" + repeatLoginsMode + ", overflowLogoutMode=" + overflowLogoutMode + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/enums/SaRepeatLoginsMode.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/enums/SaRepeatLoginsMode.java new file mode 100644 index 00000000..8458c883 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/parameter/enums/SaRepeatLoginsMode.java @@ -0,0 +1,21 @@ +package cn.dev33.satoken.stp.parameter.enums; + +/** + * SaRepeatLoginsMode: 重复登录模式 + * @author 石泽旭 + * @since 1.44.0 + */ +public enum SaRepeatLoginsMode { + + /** + * 将旧会话踢出 + */ + KICKOUT, + + /** + * 拦截新的登录会话 + */ + INTERCEPT + + +}