feat: 新增 SaLogoutParameter,用于控制注销会话时的各种细节 & 优化注销会话相关 API & SaLoginParameter 新增 replacedMode、overflowLogoutMode & SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE 默认值改为 DEF

This commit is contained in:
click33
2025-03-13 23:58:18 +08:00
parent c7f27e393e
commit 79016e5ffe
31 changed files with 969 additions and 303 deletions

View File

@@ -22,7 +22,7 @@ import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
/**

View File

@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
/**

View File

@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;

View File

@@ -15,7 +15,7 @@
*/
package cn.dev33.satoken.listener;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
/**
* Sa-Token 侦听器,默认空实现

View File

@@ -88,6 +88,11 @@ public class SaSession implements SaSetValueInterface, Serializable {
*/
private String token;
/**
* 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*/
private int historyTerminalCount;
/**
* 此 SaSession 的创建时间13位时间戳
*/
@@ -330,6 +335,8 @@ public class SaSession implements SaSetValueInterface, Serializable {
terminalList.remove(oldTerminal);
}
// 然后添加新的
this.historyTerminalCount++;
terminalInfo.setIndex(this.historyTerminalCount);
terminalList.add(terminalInfo);
update();
}
@@ -347,20 +354,24 @@ public class SaSession implements SaSetValueInterface, Serializable {
}
/**
* 获取最大的终端索引值如无返0
* 获取 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*
* @return /
*/
public int maxTerminalIndex() {
int max = 0;
for (SaTerminalInfo terminal : terminalListCopy()) {
int index = terminal.getIndex();
if (index > max) {
max = index;
}
}
return max;
public int getHistoryTerminalCount() {
return this.historyTerminalCount;
}
/**
* 设置 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*
* @param historyTerminalCount /
*/
public void setHistoryTerminalCount(int historyTerminalCount) {
this.historyTerminalCount = historyTerminalCount;
}
/**
* 判断指定设备 id 是否为可信任设备
* @param deviceId /

View File

@@ -16,6 +16,7 @@
package cn.dev33.satoken.stp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import java.util.Map;

View File

@@ -15,6 +15,8 @@
*/
package cn.dev33.satoken.stp;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
/**
* <h2> 请更改为 SaLoginParameter </h2>
* 在调用 `StpUtil.login()` 时的 配置参数 Model决定登录的一些细节行为 <br>

View File

@@ -31,6 +31,11 @@ import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.model.wrapperInfo.SaDisableWrapperInfo;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaTerminalInfo;
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.SaReplacedMode;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@@ -473,7 +478,6 @@ public class StpLogic {
// 4、在 Account-Session 上记录本次登录的终端信息
SaTerminalInfo terminalInfo = new SaTerminalInfo()
.setIndex(session.maxTerminalIndex() + 1)
.setDeviceType(loginParameter.getDeviceType())
.setDeviceId(loginParameter.getDeviceId())
.setTokenValue(tokenValue)
@@ -500,7 +504,7 @@ public class StpLogic {
// 9、检查此账号会话数量是否超出最大值如果超过则按照登录时间顺序把最开始登录的给注销掉
if(loginParameter.getMaxLoginCount() != -1) {
logoutByMaxLoginCount(id, session, null, loginParameter.getMaxLoginCount());
logoutByMaxLoginCount(id, session, null, loginParameter.getMaxLoginCount(), loginParameter.getOverflowLogoutMode());
}
// 10、一切处理完毕返回会话凭证 token
@@ -519,8 +523,12 @@ public class StpLogic {
// 1、获取全局配置的 isConcurrent 参数
// 如果配置为:不允许一个账号多地同时登录,则需要先将这个账号的历史登录会话标记为:被顶下线
if( ! loginParameter.getIsConcurrent()) {
// TODO 此处应该加一个配置决定是只顶掉当前设备类型,还是所有类型
replaced(id, loginParameter.getDeviceType());
if(loginParameter.getReplacedMode() == SaReplacedMode.CURR_DEVICE_TYPE) {
replaced(id, loginParameter.getDeviceTypeOrDefault());
}
if(loginParameter.getReplacedMode() == SaReplacedMode.ALL_DEVICE_TYPE) {
replaced(id, createSaLogoutParameter());
}
}
// 2、如果调用者预定了要生成的 token则直接返回这个预定的值框架无需再操心了
@@ -612,12 +620,19 @@ public class StpLogic {
return tokenValue;
}
// --- 注销
/**
// --- 注销 (根据 token)
/**
* 在当前客户端会话注销
*/
public void logout() {
logout(createSaLogoutParameter());
}
/**
* 在当前客户端会话注销,根据注销参数
*/
public void logout(SaLogoutParameter logoutParameter) {
// 1、如果本次请求连 Token 都没有提交,那么它本身也不属于登录状态,此时无需执行任何操作
String tokenValue = getTokenValue();
if(SaFoxUtil.isEmpty(tokenValue)) {
@@ -649,67 +664,338 @@ public class StpLogic {
storage.delete(SaTokenConsts.TOKEN_ACTIVE_TIMEOUT_CHECKED_KEY);
// 5、清除这个 token 的其它相关信息
logoutByTokenValue(tokenValue);
if(logoutParameter.getRange() == SaLogoutRange.TOKEN) {
logoutByTokenValue(tokenValue, logoutParameter);
} else {
Object loginId = getLoginIdByTokenNotThinkFreeze(tokenValue);
if(loginId != null) {
if(!logoutParameter.getIsKeepFreezeOps() && isFreeze(tokenValue)) {
return;
}
logout(loginId, logoutParameter);
}
}
}
/**
* 会话注销根据账号id
* 注销下线,根据指定 token
*
* @param loginId 账号id
* @param tokenValue 指定 token
*/
public void logoutByTokenValue(String tokenValue) {
logoutByTokenValue(tokenValue, createSaLogoutParameter());
}
/**
* 注销下线,根据指定 token、注销参数
*
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public void logoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
_logoutByTokenValue(tokenValue, logoutParameter.setMode(SaLogoutMode.LOGOUT));
}
/**
* 踢人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
*/
public void kickoutByTokenValue(String tokenValue) {
kickoutByTokenValue(tokenValue, createSaLogoutParameter());
}
/**
* 踢人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter 注销参数
*/
public void kickoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
_logoutByTokenValue(tokenValue, logoutParameter.setMode(SaLogoutMode.KICKOUT));
}
/**
* 顶人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
*/
public void replacedByTokenValue(String tokenValue) {
replacedByTokenValue(tokenValue, createSaLogoutParameter());
}
/**
* 顶人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public void replacedByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
_logoutByTokenValue(tokenValue, logoutParameter.setMode(SaLogoutMode.REPLACED));
}
/**
* [work] 注销下线,根据指定 token 、注销参数
*
* @param tokenValue 指定 token
* @param logoutParameter 注销参数
*/
public void _logoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
// 1、判断一下如果此 token 映射的是一个无效 loginId则此处立即返回不需要再往下处理了
// 如果不提前截止,则后续的操作可能会写入意外数据
Object loginId = getLoginIdByTokenNotThinkFreeze(tokenValue);
if( SaFoxUtil.isEmpty(loginId) ) {
return;
}
if(!logoutParameter.getIsKeepFreezeOps() && isFreeze(tokenValue)) {
return;
}
// 2、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 3、清除 Token-Session
if( ! logoutParameter.getIsKeepTokenSession()) {
deleteTokenSession(tokenValue);
}
// 4、清理或更改 Token 映射
// 5、发布事件通知
// SaLogoutMode.LOGOUT注销下线
if(logoutParameter.getMode() == SaLogoutMode.LOGOUT) {
deleteTokenToIdMapping(tokenValue);
SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
}
// SaLogoutMode.LOGOUT踢人下线
if(logoutParameter.getMode() == SaLogoutMode.KICKOUT) {
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
SaTokenEventCenter.doKickout(loginType, loginId, tokenValue);
}
// SaLogoutMode.REPLACED顶人下线
if(logoutParameter.getMode() == SaLogoutMode.REPLACED) {
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
SaTokenEventCenter.doReplaced(loginType, loginId, tokenValue);
}
// 6、清理这个账号的 Account-Session 上的 terminal 信息,并且尝试注销掉 Account-Session
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
session.removeTerminal(tokenValue);
session.logoutByTerminalCountToZero();
}
}
// --- 注销 (根据 loginId)
/**
* 会话注销根据账号id
*
* @param loginId 账号id
*/
public void logout(Object loginId) {
logout(loginId, null);
logout(loginId, createSaLogoutParameter());
}
/**
* 会话注销根据账号id 和 设备类型
*
* @param loginId 账号id
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表注销该账号的所有设备类型)
*/
public void logout(Object loginId, String deviceType) {
logout(loginId, createSaLogoutParameter().setDeviceType(deviceType));
}
/**
* 会话注销根据账号id 和 注销参数
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public void logout(Object loginId, SaLogoutParameter logoutParameter) {
_logout(loginId, logoutParameter.setMode(SaLogoutMode.LOGOUT));
}
/**
* 踢人下线根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
*/
public void kickout(Object loginId) {
kickout(loginId, createSaLogoutParameter());
}
/**
* 踢人下线根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表踢出该账号的所有设备类型)
*/
public void kickout(Object loginId, String deviceType) {
kickout(loginId, createSaLogoutParameter().setDeviceType(deviceType));
}
/**
* 踢人下线根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public void kickout(Object loginId, SaLogoutParameter logoutParameter) {
_logout(loginId, logoutParameter.setMode(SaLogoutMode.KICKOUT));
}
/**
* 顶人下线根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
*/
public void replaced(Object loginId) {
replaced(loginId, createSaLogoutParameter());
}
/**
* 顶人下线根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表顶替该账号的所有设备类型)
*/
public void replaced(Object loginId, String deviceType) {
replaced(loginId, createSaLogoutParameter().setDeviceType(deviceType));
}
/**
* 顶人下线根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public void replaced(Object loginId, SaLogoutParameter logoutParameter) {
_logout(loginId, logoutParameter.setMode(SaLogoutMode.REPLACED));
}
/**
* [work] 会话注销根据账号id 和 注销参数
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public void _logout(Object loginId, SaLogoutParameter logoutParameter) {
// 1、获取此账号的 Account-Session上面记录了此账号的所有登录客户端数据
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
// 2、遍历此账号所有从这个 deviceType 设备类型上登录的客户端,清除相关数据
for (SaTerminalInfo terminal: session.getTerminalListByDeviceType(deviceType)) {
// 2.1、获取此客户端的 token 值
String tokenValue = terminal.getTokenValue();
// 2.2、从 Account-Session 上清除此设备信息
session.removeTerminal(tokenValue);
// 2.3、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 2.4、清除 token -> id 的映射关系
deleteTokenToIdMapping(tokenValue);
// 2.5、清除这个 token 的 Token-Session 对象
deleteTokenSession(tokenValue);
// 2.6、$$ 发布事件xx 账号的 xx 客户端注销了
SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
// 2、遍历此 SaTerminalInfo 客户端列表,清除相关数据
List<SaTerminalInfo> terminalList = session.getTerminalListByDeviceType(logoutParameter.getDeviceType());
for (SaTerminalInfo terminal: terminalList) {
_removeTerminal(session, terminal, logoutParameter);
}
// 3、如果代码走到这里的时候此账号已经没有客户端在登录了则直接注销掉这个 Account-Session
session.logoutByTerminalCountToZero();
if(logoutParameter.getMode() == SaLogoutMode.REPLACED) {
// 因为调用顶替下线时,一般都是在新客户端正在登录,所以此种情况不需要清除该账号的 Account-Session
// 如果清除了 Account-Session将可能导致 Account-Session 被注销后又立刻创建出来,造成不必要的性能浪费
} else {
session.logoutByTerminalCountToZero();
}
}
}
// --- 注销 (会话管理辅助方法)
/**
* 在 Account-Session 上移除 Terminal 信息 (注销下线方式)
* @param session /
* @param terminal /
*/
public void removeTerminalByLogout(SaSession session, SaTerminalInfo terminal) {
_removeTerminal(session, terminal, createSaLogoutParameter().setMode(SaLogoutMode.LOGOUT));
}
/**
* 在 Account-Session 上移除 Terminal 信息 (踢人下线方式)
* @param session /
* @param terminal /
*/
public void removeTerminalByKickout(SaSession session, SaTerminalInfo terminal) {
_removeTerminal(session, terminal, createSaLogoutParameter().setMode(SaLogoutMode.KICKOUT));
}
/**
* 在 Account-Session 上移除 Terminal 信息 (顶人下线方式)
* @param session /
* @param terminal /
*/
public void removeTerminalByReplaced(SaSession session, SaTerminalInfo terminal) {
_removeTerminal(session, terminal, createSaLogoutParameter().setMode(SaLogoutMode.REPLACED));
}
/**
* 在 Account-Session 上移除 Terminal 信息 (内部方法,仅为减少重复代码,外部调用意义不大)
* @param session Account-Session
* @param terminal 设备信息
* @param logoutParameter 注销参数
*/
public void _removeTerminal(SaSession session, SaTerminalInfo terminal, SaLogoutParameter logoutParameter) {
Object loginId = session.getLoginId();
String tokenValue = terminal.getTokenValue();
// 1、从 Account-Session 上清除此设备信息
session.removeTerminal(tokenValue);
// 2、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 3、清除这个 token 的 Token-Session 对象
if( ! logoutParameter.getIsKeepTokenSession()) {
deleteTokenSession(tokenValue);
}
// 4、清理或更改 Token 映射
// 5、发布事件通知
// SaLogoutMode.LOGOUT注销下线
if(logoutParameter.getMode() == SaLogoutMode.LOGOUT) {
deleteTokenToIdMapping(tokenValue);
SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
}
// SaLogoutMode.LOGOUT踢人下线
if(logoutParameter.getMode() == SaLogoutMode.KICKOUT) {
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
SaTokenEventCenter.doKickout(loginType, loginId, tokenValue);
}
// SaLogoutMode.REPLACED顶人下线
if(logoutParameter.getMode() == SaLogoutMode.REPLACED) {
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
SaTokenEventCenter.doReplaced(loginType, loginId, tokenValue);
}
}
/**
* 如果指定账号 id、设备类型的登录客户端已经超过了指定数量则按照登录时间顺序把最开始登录的给注销掉
*
* @param loginId 账号id
*
* @param loginId 账号id
* @param session 此账号的 Account-Session 对象,可填写 null框架将自动获取
* @param deviceType 设备类型(填 null 代表注销此账号所有设备类型的登录)
* @param maxLoginCount 最大登录数量,超过此数量的将被注销
* @param logoutMode 超出的客户端将以何种方式被注销
*/
public void logoutByMaxLoginCount(Object loginId, SaSession session, String deviceType, int maxLoginCount) {
public void logoutByMaxLoginCount(Object loginId, SaSession session, String deviceType, int maxLoginCount, SaLogoutMode logoutMode) {
// 1、如果调用者提供的 Account-Session 对象为空,则我们先手动获取一下
if(session == null) {
@@ -724,196 +1010,14 @@ public class StpLogic {
// 3、按照登录时间倒叙超过 maxLoginCount 数量的,全部注销掉
for (int i = 0; i < list.size() - maxLoginCount; i++) {
// 3.1、获取此客户端的 token 值
String tokenValue = list.get(i).getTokenValue();
// 3.2、从 Account-Session 上清除 terminal 信息
session.removeTerminal(tokenValue);
// 3.3、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 3.4、清除 token -> id 的映射关系
deleteTokenToIdMapping(tokenValue);
// 3.5、清除这个 token 的 Token-Session 对象
deleteTokenSession(tokenValue);
// 3.6、$$ 发布事件xx 账号的 xx 客户端注销了
SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
_removeTerminal(session, list.get(i), createSaLogoutParameter().setMode(logoutMode));
}
// 4、如果代码走到这里的时候此账号已经没有客户端在登录了则直接注销掉这个 Account-Session
session.logoutByTerminalCountToZero();
}
/**
* 会话注销,根据指定 Token
*
* @param tokenValue 指定 token
*/
public void logoutByTokenValue(String tokenValue) {
// 1、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 2、清除这个 token 的 Token-Session 对象
deleteTokenSession(tokenValue);
// 3、清除 token -> id 的映射关系
String loginId = getLoginIdNotHandle(tokenValue);
if(loginId != null) {
deleteTokenToIdMapping(tokenValue);
}
// 4、判断一下如果此 token 映射的是一个无效 loginId则此处立即返回不需要再往下处理了
if( ! isValidLoginId(loginId) ) {
return;
}
// 5、$$ 发布事件:某某账号的某某 token 注销下线了
SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
// 6、清理这个账号的 Account-Session 上的 terminal 信息,并且尝试注销掉 Account-Session
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
session.removeTerminal(tokenValue);
session.logoutByTerminalCountToZero();
}
}
/**
* 踢人下线根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
*/
public void kickout(Object loginId) {
kickout(loginId, null);
}
/**
* 踢人下线根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表踢出该账号的所有设备类型)
*/
public void kickout(Object loginId, String deviceType) {
// 1、获取此账号的 Account-Session上面记录了此账号的所有登录客户端数据
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
// 2、遍历此账号所有从这个 deviceType 设备上登录的客户端,清除相关数据
for (SaTerminalInfo terminal: session.getTerminalListByDeviceType(deviceType)) {
// 2.1、获取此客户端的 token 值
String tokenValue = terminal.getTokenValue();
// 2.2、从 Account-Session 上清除 terminal 信息
session.removeTerminal(tokenValue);
// 2.3、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 2.4、将此 token 标记为:已被踢下线
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
// 2.5、清除 Token-Session
deleteTokenSession(tokenValue);
// 2.6、$$ 发布事件xx 账号的 xx 客户端被踢下线了
SaTokenEventCenter.doKickout(loginType, loginId, tokenValue);
}
// 3、如果代码走到这里的时候此账号已经没有客户端在登录了则直接注销掉这个 Account-Session
session.logoutByTerminalCountToZero();
}
}
/**
* 踢人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
*/
public void kickoutByTokenValue(String tokenValue) {
// 1、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 2、清除 Token-Session
deleteTokenSession(tokenValue);
// 3、判断一下如果此 token 映射的是一个无效 loginId则此处立即返回不需要再往下处理了
String loginId = getLoginIdNotHandle(tokenValue);
if( ! isValidLoginId(loginId) ) {
return;
}
// 4、将此 token 标记为:已被踢下线
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
// 5、$$. 发布事件:某某 token 被踢下线了
SaTokenEventCenter.doKickout(loginType, loginId, tokenValue);
// 6、清理这个账号的 Account-Session 上的 terminal 信息,并且尝试注销掉 Account-Session
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
session.removeTerminal(tokenValue);
session.logoutByTerminalCountToZero();
}
}
/**
* 顶人下线根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表顶替该账号的所有设备类型)
*/
public void replaced(Object loginId, String deviceType) {
// 1、获取此账号的 Account-Session上面记录了此账号的所有登录客户端数据
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
// 2、遍历此账号所有从这个 deviceType 设备上登录的客户端,清除相关数据
for (SaTerminalInfo ter: session.getTerminalListByDeviceType(deviceType)) {
// 2.1、获取此客户端的 token 值
String tokenValue = ter.getTokenValue();
// 2.2、从 Account-Session 上清除 terminal 信息
session.removeTerminal(tokenValue);
// 2.3、清除这个 token 的最后活跃时间记录
if(isOpenCheckActiveTimeout()) {
clearLastActive(tokenValue);
}
// 2.4、将此 token 标记为:已被顶下线
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
// 2.5、清除 Token-Session 对象
deleteTokenSession(tokenValue);
// 2.6、$$ 发布事件xx 账号的 xx 客户端注销了
SaTokenEventCenter.doReplaced(loginType, loginId, tokenValue);
}
// 3、因为调用顶替下线时一般都是在新客户端正在登录所以此处不需要清除该账号的 Account-Session
// 如果此处清除了 Account-Session将可能导致 Account-Session 被注销后又立刻创建出来,造成不必要的性能浪费
// session.logoutByTerminalCountToZero();
}
}
// ---- 会话查询
/**
@@ -1092,28 +1196,49 @@ public class StpLogic {
}
/**
* 获取指定 token 对应的账号id如果未登录,则返回 null
* 获取指定 token 对应的账号id如果 token 无效或 token 处于被踢、被顶、被冻结等状态,则返回 null
*
* @param tokenValue token
* @return 账号id
*/
public Object getLoginIdByToken(String tokenValue) {
// 1、如果提供的 token 为空,则直接返回 null
if(SaFoxUtil.isEmpty(tokenValue)) {
return null;
}
Object loginId = getLoginIdByTokenNotThinkFreeze(tokenValue);
// 2、查找此 token 对应的 loginId如果找不到或找的到但属于无效值则返回 null
String loginId = getLoginIdNotHandle(tokenValue);
if( ! isValidLoginId(loginId) ) {
return null;
}
if( SaFoxUtil.isNotEmpty(loginId) ) {
// 如果 token 已被冻结,也返回 null
long activeTimeout = getTokenActiveTimeoutByToken(tokenValue);
if(activeTimeout == SaTokenDao.NOT_VALUE_EXPIRE) {
return null;
}
}
// 3、返回
return loginId;
}
/**
* 获取指定 token 对应的账号id如果 token 无效或 token 处于被踢、被顶等状态 (不考虑被冻结),则返回 null
*
* @param tokenValue token
* @return 账号id
*/
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
// 1、如果提供的 token 为空,则直接返回 null
if(SaFoxUtil.isEmpty(tokenValue)) {
return null;
}
// 2、查找此 token 对应的 loginId如果找不到或找的到但属于无效值则返回 null
String loginId = getLoginIdNotHandle(tokenValue);
if( ! isValidLoginId(loginId) ) {
return null;
}
// 3、返回
return loginId;
}
/**
* 获取指定 token 对应的账号id (不做任何特殊处理)
*
@@ -1530,24 +1655,36 @@ public class StpLogic {
protected void clearLastActive(String tokenValue) {
getSaTokenDao().delete(splicingKeyLastActiveTime(tokenValue));
}
/**
/**
* 判断指定 token 是否已被冻结
*
* @param tokenValue 指定 token
*/
public boolean isFreeze(String tokenValue) {
// 1、获取这个 token 的剩余活跃有效期
long activeTimeout = getTokenActiveTimeoutByToken(tokenValue);
// 2、值为 -1 代表此 token 已经被设置永不冻结
if(activeTimeout == SaTokenDao.NEVER_EXPIRE) {
return false;
}
// 3、值为 -2 代表已被冻结
if(activeTimeout == SaTokenDao.NOT_VALUE_EXPIRE) {
return true;
}
return false;
}
/**
* 检查指定 token 是否已被冻结,如果是则抛出异常
*
* @param tokenValue 指定 token
*/
public void checkActiveTimeout(String tokenValue) {
// 1、获取这个 token 的剩余活跃有效期
long activeTimeout = getTokenActiveTimeoutByToken(tokenValue);
// 2、值为 -1 代表此 token 已经被设置永不冻结,无须继续验证
if(activeTimeout == SaTokenDao.NEVER_EXPIRE) {
return;
}
// 3、值为 -2 代表已被冻结,此时需要抛出异常
if(activeTimeout == SaTokenDao.NOT_VALUE_EXPIRE) {
if (isFreeze(tokenValue)) {
throw NotLoginException.newInstance(loginType, TOKEN_FREEZE, TOKEN_FREEZE_MESSAGE, tokenValue).setCode(SaErrorCode.CODE_11016);
}
}
@@ -2929,6 +3066,15 @@ public class StpLogic {
return new SaLoginParameter(getConfigOrGlobal());
}
/**
* 根据当前配置对象创建一个 SaLogoutParameter 对象
*
* @return /
*/
public SaLogoutParameter createSaLogoutParameter() {
return new SaLogoutParameter(getConfigOrGlobal());
}
// ------------------- 过期方法 -------------------

View File

@@ -20,6 +20,8 @@ import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaTerminalInfo;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import java.util.List;
@@ -235,7 +237,7 @@ public class StpUtil {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
// --- 注销 (根据 token)
/**
* 在当前客户端会话注销
@@ -245,7 +247,77 @@ public class StpUtil {
}
/**
* 会话注销,根据账号id
* 在当前客户端会话注销,根据注销参数
*/
public static void logout(SaLogoutParameter logoutParameter) {
stpLogic.logout(logoutParameter);
}
/**
* 注销下线,根据指定 token
*
* @param tokenValue 指定 token
*/
public static void logoutByTokenValue(String tokenValue) {
stpLogic.logoutByTokenValue(tokenValue);
}
/**
* 注销下线,根据指定 token、注销参数
*
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public static void logoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.logoutByTokenValue(tokenValue, logoutParameter);
}
/**
* 踢人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
*/
public static void kickoutByTokenValue(String tokenValue) {
stpLogic.kickoutByTokenValue(tokenValue);
}
/**
* 踢人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter 注销参数
*/
public static void kickoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.kickoutByTokenValue(tokenValue, logoutParameter);
}
/**
* 顶人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
*/
public static void replacedByTokenValue(String tokenValue) {
stpLogic.replacedByTokenValue(tokenValue);
}
/**
* 顶人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public static void replacedByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.replacedByTokenValue(tokenValue, logoutParameter);
}
// --- 注销 (根据 loginId)
/**
* 会话注销根据账号id
*
* @param loginId 账号id
*/
@@ -256,7 +328,7 @@ public class StpUtil {
/**
* 会话注销根据账号id 和 设备类型
*
* @param loginId 账号id
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表注销该账号的所有设备类型)
*/
public static void logout(Object loginId, String deviceType) {
@@ -264,19 +336,20 @@ public class StpUtil {
}
/**
* 会话注销,根据指定 Token
* 会话注销,根据账号id 和 注销参数
*
* @param tokenValue 指定 token
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void logoutByTokenValue(String tokenValue) {
stpLogic.logoutByTokenValue(tokenValue);
public static void logout(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.logout(loginId, logoutParameter);
}
/**
* 踢人下线根据账号id
* 踢人下线根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param loginId 账号id
*/
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
@@ -294,13 +367,24 @@ public class StpUtil {
}
/**
* 踢人下线,根据指定 token
* 踢人下线,根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void kickoutByTokenValue(String tokenValue) {
stpLogic.kickoutByTokenValue(tokenValue);
public static void kickout(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.kickout(loginId, logoutParameter);
}
/**
* 顶人下线根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
*/
public static void replaced(Object loginId) {
stpLogic.replaced(loginId);
}
/**
@@ -314,6 +398,47 @@ public class StpUtil {
stpLogic.replaced(loginId, deviceType);
}
/**
* 顶人下线根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void replaced(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.replaced(loginId, logoutParameter);
}
// --- 注销 (会话管理辅助方法)
/**
* 在 Account-Session 上移除 Terminal 信息 (注销下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByLogout(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByLogout(session, terminal);
}
/**
* 在 Account-Session 上移除 Terminal 信息 (踢人下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByKickout(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByKickout(session, terminal);
}
/**
* 在 Account-Session 上移除 Terminal 信息 (顶人下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByReplaced(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByReplaced(session, terminal);
}
// 会话查询
/**
@@ -398,7 +523,7 @@ public class StpUtil {
}
/**
* 获取指定 token 对应的账号id如果未登录,则返回 null
* 获取指定 token 对应的账号id如果 token 无效或 token 处于被踢、被顶、被冻结等状态,则返回 null
*
* @param tokenValue token
* @return 账号id
@@ -407,6 +532,16 @@ public class StpUtil {
return stpLogic.getLoginIdByToken(tokenValue);
}
/**
* 获取指定 token 对应的账号id如果 token 无效或 token 处于被踢、被顶等状态 (不考虑被冻结),则返回 null
*
* @param tokenValue token
* @return 账号id
*/
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
}
/**
* 获取当前 Token 的扩展信息此函数只在jwt模式下生效
*

View File

@@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp;
package cn.dev33.satoken.stp.parameter;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedMode;
import cn.dev33.satoken.util.SaTokenConsts;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 在调用 `StpUtil.login()` 时的 配置参数 Model决定登录的一些细节行为 <br>
* 在调用 `StpUtil.login()` 时的 配置参数对象决定登录的一些细节行为 <br>
*
* <pre>
* // 例如在登录时指定 token 有效期为七天代码如下
@@ -48,6 +50,16 @@ public class SaLoginParameter {
*/
private String deviceId;
/**
* 顶人下线的范围
*/
private SaReplacedMode replacedMode = SaReplacedMode.CURR_DEVICE_TYPE;
/**
* 溢出 maxLoginCount 的客户端将以何种方式注销下线
*/
private SaLogoutMode overflowLogoutMode = SaLogoutMode.LOGOUT;
/**
* 扩展信息只在 jwt 模式下生效
*/
@@ -123,7 +135,7 @@ public class SaLoginParameter {
* @return 对象自身
*/
public SaLoginParameter setDefaultValues(SaTokenConfig config) {
this.deviceType = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
this.deviceType = SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE;
this.timeout = config.getTimeout();
this.isConcurrent = config.getIsConcurrent();
this.isShare = config.getIsShare();
@@ -220,9 +232,9 @@ public class SaLoginParameter {
/**
* @return 获取device参数如果为null则返回默认值
*/
public String getDeviceOrDefault() {
public String getDeviceTypeOrDefault() {
if(deviceType == null) {
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
return SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE;
}
return deviceType;
}
@@ -274,6 +286,45 @@ public class SaLoginParameter {
return this;
}
/**
* 获取 顶人下线的范围
*
* @return replacedMode 顶人下线的范围
*/
public SaReplacedMode getReplacedMode() {
return this.replacedMode;
}
/**
* 设置 顶人下线的范围
*
* @param replacedMode /
* @return 对象自身
*/
public SaLoginParameter setReplacedMode(SaReplacedMode replacedMode) {
this.replacedMode = replacedMode;
return this;
}
/**
* 获取 溢出 maxLoginCount 的客户端将以何种方式注销下线
*
* @return overflowLogoutMode /
*/
public SaLogoutMode getOverflowLogoutMode() {
return this.overflowLogoutMode;
}
/**
* 设置 溢出 maxLoginCount 的客户端将以何种方式注销下线
*
* @param overflowLogoutMode /
* @return 对象自身
*/
public SaLoginParameter setOverflowLogoutMode(SaLogoutMode overflowLogoutMode) {
this.overflowLogoutMode = overflowLogoutMode;
return this;
}
/**
* @return 是否为持久Cookie临时Cookie在浏览器关闭时会自动删除持久Cookie在重新打开后依然存在
*/
@@ -462,6 +513,8 @@ public class SaLoginParameter {
return "SaLoginParameter ["
+ "deviceType=" + deviceType
+ ", deviceId=" + deviceId
+ ", replacedMode=" + replacedMode
+ ", overflowLogoutMode=" + overflowLogoutMode
+ ", isLastingCookie=" + isLastingCookie
+ ", timeout=" + timeout
+ ", activeTimeout=" + activeTimeout

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
/**
* 在会话注销时的 配置参数对象,决定注销时的一些细节行为 <br>
*
* <pre>
* // 例如:
* StpUtil.logout(10001, new SaLogoutParameter());
* </pre>
*
* @author click33
* @since 1.41.0
*/
public class SaLogoutParameter {
/**
* 是否保留 Token-Session
*/
private Boolean isKeepTokenSession = false;
/**
* 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
*/
private Boolean isKeepFreezeOps = false;
/**
* 设备类型 (如果不指定,则默认注销所有客户端)
*/
private String deviceType;
/**
* 注销类型
*/
private SaLogoutMode mode = SaLogoutMode.LOGOUT;
/**
* 注销范围 (此参数只在调用 StpUtil.logout(new SaLogoutParameter()) 时有效)
*/
private SaLogoutRange range = SaLogoutRange.TOKEN;
// ------ 附加方法
public SaLogoutParameter() {
this(SaManager.getConfig());
}
public SaLogoutParameter(SaTokenConfig config) {
setDefaultValues(config);
}
/**
* 根据 SaTokenConfig 对象初始化默认值
*
* @param config 使用的配置对象
* @return 对象自身
*/
public SaLogoutParameter setDefaultValues(SaTokenConfig config) {
return this;
}
/**
* 静态方法获取一个 SaLoginParameter 对象
* @return SaLoginParameter 对象
*/
public static SaLogoutParameter create() {
return new SaLogoutParameter(SaManager.getConfig());
}
// ---------------- get set
/**
* @return 是否保留 Token-Session
*/
public Boolean getIsKeepTokenSession() {
return isKeepTokenSession;
}
/**
* @param isKeepTokenSession 是否保留 Token-Session
*
* @return 对象自身
*/
public SaLogoutParameter setIsKeepTokenSession(Boolean isKeepTokenSession) {
this.isKeepTokenSession = isKeepTokenSession;
return this;
}
/**
* 获取 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
*
* @return /
*/
public Boolean getIsKeepFreezeOps() {
return this.isKeepFreezeOps;
}
/**
* 设置 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
*
* @param isKeepFreezeOps /
* @return 对象自身
*/
public SaLogoutParameter setIsKeepFreezeOps(Boolean isKeepFreezeOps) {
this.isKeepFreezeOps = isKeepFreezeOps;
return this;
}
/**
* 获取 设备类型 (如果不指定,则默认注销所有客户端)
*
* @return deviceType /
*/
public String getDeviceType() {
return this.deviceType;
}
/**
* 设置 设备类型 (如果不指定,则默认注销所有客户端)
*
* @param deviceType /
* @return /
*/
public SaLogoutParameter setDeviceType(String deviceType) {
this.deviceType = deviceType;
return this;
}
/**
* 获取 注销类型
*
* @return logoutMode 注销类型
*/
public SaLogoutMode getMode() {
return this.mode;
}
/**
* 设置 注销类型
*
* @param mode 注销类型
* @return /
*/
public SaLogoutParameter setMode(SaLogoutMode mode) {
this.mode = mode;
return this;
}
/**
* 获取 注销范围 (此参数只在调用 StpUtil.logout(new SaLogoutParameter()) 时有效)
*
* @return /
*/
public SaLogoutRange getRange() {
return this.range;
}
/**
* 设置 注销范围 (此参数只在调用 StpUtil.logout(new SaLogoutParameter()) 时有效)
*
* @param range /
* @return /
*/
public SaLogoutParameter setRange(SaLogoutRange range) {
this.range = range;
return this;
}
/*
* toString
*/
@Override
public String toString() {
return "SaLoginParameter ["
+ "deviceType=" + deviceType
+ ", isKeepTokenSession=" + isKeepTokenSession
+ ", isKeepFreezeOps=" + isKeepFreezeOps
+ ", mode=" + mode
+ ", range=" + range
+ "]";
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* SaLogoutMode: 注销模式
*
* @author click33
* @since 1.41.0
*/
public enum SaLogoutMode {
/**
* 注销下线
*/
LOGOUT,
/**
* 踢人下线
*/
KICKOUT,
/**
* 顶人下线
*/
REPLACED;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* SaLogoutMode: 注销范围
*
* @author click33
* @since 1.41.0
*/
public enum SaLogoutRange {
/**
* token 范围:只注销提供的 token 指向的会话
*/
TOKEN,
/**
* 账号范围:注销 token 指向的 loginId 会话
*/
ACCOUNT
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* 顶人下线的范围
*
* @author click33
* @since 1.41.0
*/
public enum SaReplacedMode {
/**
* 当前指定的设备类型端
*/
CURR_DEVICE_TYPE,
/**
* 所有设备类型端
*/
ALL_DEVICE_TYPE
}

View File

@@ -74,7 +74,7 @@ public class SaTokenConsts {
/**
* 常量 key 标记: 在登录时,默认使用的设备类型
*/
public static final String DEFAULT_LOGIN_DEVICE = "default-device";
public static final String DEFAULT_LOGIN_DEVICE_TYPE = "DEF";
/**
* 常量 key 标记: 在封禁账号时,默认封禁的服务类型