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 标记: 在封禁账号时默认封禁的服务类型

View File

@ -1,7 +1,7 @@
package com.pj.satoken;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
/**
* Sa-Token 自定义侦听器的实现

View File

@ -5,7 +5,7 @@ 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.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;

View File

@ -13,7 +13,7 @@ sa-token:
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token
is-share: true
is-share: false
# token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: uuid
# 是否输出操作日志

View File

@ -1,6 +1,6 @@
package com.pj.test;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.pj.util.DeviceLockCheckUtil;

View File

@ -13,7 +13,7 @@ sa-token:
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token
is-share: true
is-share: false
# token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: uuid
# 是否输出操作日志

View File

@ -60,7 +60,7 @@
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -4,7 +4,7 @@ import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;

View File

@ -1,6 +1,7 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -20,6 +21,7 @@ public class LoginController {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
StpUtil.getTokenSession();
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
@ -47,7 +49,7 @@ public class LoginController {
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
StpUtil.login(10001, SaLoginParameter.create().setIsConcurrent(false));
return SaResult.ok();
}

View File

@ -4,7 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.annotation.SaCheckSign;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;

View File

@ -23,7 +23,7 @@ import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.jwt.error.SaJwtErrorCode;
import cn.dev33.satoken.jwt.exception.SaJwtException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;

View File

@ -22,7 +22,7 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.jwt.error.SaJwtErrorCode;
import cn.dev33.satoken.jwt.exception.SaJwtException;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;

View File

@ -59,13 +59,13 @@ public class JwtForMixinTest {
Assertions.assertTrue(StpUtil.isLogin());
Assertions.assertNotNull(token); // token不为null
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assertions.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_ID), "10001"); // 账号
Assertions.assertEquals(payloads.getStr(SaJwtUtil.DEVICE_TYPE), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(payloads.getStr(SaJwtUtil.DEVICE_TYPE), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
Assertions.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_TYPE), StpUtil.TYPE); // 账号类型
// db数据 验证

View File

@ -54,7 +54,7 @@ public class JwtForSimpleTest {
Assertions.assertTrue(StpUtil.isLogin());
Assertions.assertNotNull(token); // token不为null
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
// token 验证
JWT jwt = JWT.of(token);

View File

@ -55,13 +55,13 @@ public class JwtForStatelessTest {
Assertions.assertTrue(StpUtil.isLogin());
Assertions.assertNotNull(token); // token不为null
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assertions.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_ID), "10001"); // 账号
Assertions.assertEquals(payloads.getStr(SaJwtUtil.DEVICE_TYPE), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(payloads.getStr(SaJwtUtil.DEVICE_TYPE), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
Assertions.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_TYPE), StpUtil.TYPE); // 账号类型
// 时间

View File

@ -15,7 +15,7 @@
*/
package cn.dev33.satoken.core.stp;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.util.SaTokenConsts;
import org.junit.jupiter.api.Assertions;
@ -83,7 +83,7 @@ public class TokenInfoTest {
.create()
.setTimeout(-1);
Assertions.assertEquals(loginParameter.getCookieTimeout(), Integer.MAX_VALUE);
Assertions.assertEquals(loginParameter.getDeviceOrDefault(), SaTokenConsts.DEFAULT_LOGIN_DEVICE);
Assertions.assertEquals(loginParameter.getDeviceTypeOrDefault(), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE);
}
}

View File

@ -26,7 +26,7 @@ import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.spring.pathmatch.SaPathMatcherHolder;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@ -102,11 +102,11 @@ public class BasicsTest {
Assertions.assertNotNull(token);
Assertions.assertEquals(token, StpUtil.getTokenValueNotCut());
Assertions.assertEquals(token, StpUtil.getTokenValueByLoginId(10001));
Assertions.assertEquals(token, StpUtil.getTokenValueByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE));
Assertions.assertEquals(token, StpUtil.getTokenValueByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE));
// token 队列
List<String> tokenList = StpUtil.getTokenValueListByLoginId(10001);
List<String> tokenList2 = StpUtil.getTokenValueListByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE);
List<String> tokenList2 = StpUtil.getTokenValueListByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE);
Assertions.assertEquals(token, tokenList.get(tokenList.size() - 1));
Assertions.assertEquals(token, tokenList2.get(tokenList.size() - 1));
@ -119,7 +119,7 @@ public class BasicsTest {
Assertions.assertEquals(StpUtil.getLoginIdAsString(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginId(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginIdDefaultNull(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE); // 登录设备类型
// db数据 验证
// token存在