news 2026/9/13 14:13:09

Sa-Token 踢人下线详解:强制注销、踢人下线与顶人下线的原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token 踢人下线详解:强制注销、踢人下线与顶人下线的原理与实践

Sa-Token 踢人下线详解:强制注销、踢人下线与顶人下线的原理与实践

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

Sa-Token 是开源、免费的 Java 权限认证框架,其"踢人下线"能力用于后台治理会话:管理员或业务代码可以按账号、按设备端、按 Token 值,将指定用户的会话强制注销或踢下线。本文以 官方文档 为骨架,结合 StpLogic 核心实现 与 Demo 示例,讲清楚三种下线方式的 API 用法、场景值差异与底层调用链,读完你可以直接在业务代码中实现"后台踢人""按端注销""新登录顶掉旧设备"等常见会话治理功能。

一、什么是"踢人下线"

所谓踢人下线,核心操作就是找到指定loginId对应的Token,并设置其失效

在 Sa-Token 的数据模型中,框架会在持久层(SaTokenDao)维护一张token-value -> loginId的映射表,会话是否有效,取决于这张映射表里的数据。因此,无论按账号踢、按设备端踢还是按 Token 踢,最终落点都是清理或篡改这条映射记录

  • 清理映射:删除token-value -> loginId记录,对应"强制注销";
  • 篡改映射:把token-value -> loginId更新为一个特殊标记值,对应"踢人下线 / 顶人下线"。

映射的写入、更新、删除分别由 saveTokenToIdMapping / updateTokenToIdMapping / deleteTokenToIdMapping 完成,它们最终都委托给SaTokenDao(默认内存实现,可切换 Redis 等分布式存储),这就是三种下线方式共用的一套底层机制。

二、强制注销(logout)

强制注销的语义是:等价于对方主动调用了注销方法,让指定账号的会话立即失效。

2.1 三个核心 API

StpUtil.logout(10001); // 强制指定账号注销下线 StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线 StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线

三个方法的含义:

API作用定位粒度
StpUtil.logout(loginId)强制指定账号注销下线账号(所有设备端)
StpUtil.logout(loginId, deviceType)强制指定账号指定端注销下线账号 + 设备端
StpUtil.logoutByTokenValue(tokenValue)强制指定 Token 注销下线单条 Token

其中deviceType参数(如"PC""APP""H5")是登录时通过StpUtil.login(loginId, deviceType)指定的设备类型标记。从 StpLogic.logout(loginId, deviceType) 的源码注释可以看出:deviceTypenull代表注销该账号的所有设备类型

2.2 注销后的行为

强制注销会彻底清除 Token 信息(包括 Token 映射、Token-Session 等),对方再次访问系统时,会抛出NotLoginException异常,提示语为token 无效

三、踢人下线(kickout)

踢人下线是后台管理最常用的操作:管理员可以随时把某个可疑账号或某台设备踢下线,而不需要破坏它已经产生的数据。

3.1 三个核心 API

StpUtil.kickout(10001); // 将指定账号踢下线 StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线 StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线

用法与logout系列完全对应:

API作用定位粒度
StpUtil.kickout(loginId)将指定账号踢下线账号(所有设备端)
StpUtil.kickout(loginId, deviceType)将指定账号指定端踢下线账号 + 设备端
StpUtil.kickoutByTokenValue(tokenValue)将指定 Token 踢下线单条 Token

同样,deviceTypenull代表踢出该账号的所有设备类型(见 StpLogic.kickout(loginId, deviceType))。

3.2 强制注销 vs 踢人下线的区别

这是本主题最核心的辨析点:

  • 强制注销等价于对方主动调用了注销方法,Token 映射被彻底删除,对方再次访问会提示:Token 无效
  • 踢人下线不会清除 Token 信息,而是将其打上特定标记,对方再次访问会提示:Token 已被踢下线

"打上特定标记"在源码中体现得很直观。在 StpLogic._fireLogoutEvent 中:

// SaLogoutMode.LOGOUT:注销下线 —— 真正删除映射 deleteTokenToIdMapping(tokenValue); SaTokenEventCenter.doLogout(loginType, loginId, tokenValue); // SaLogoutMode.KICKOUT:踢人下线 —— 把映射更新为特殊标记 updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT); SaTokenEventCenter.doKickout(loginType, loginId, tokenValue);

其中NotLoginException.KICK_OUT的值是字符串"-5",定义于 NotLoginException:

/** 表示 token 已被踢下线 */ public static final String KICK_OUT = "-5"; public static final String KICK_OUT_MESSAGE = "token 已被踢下线";

也就是说,踢人下线后,DAO 中原本token -> 10001的映射变成了token -> "-5"。下次该 Token 再访问时,框架取出的 loginId 是-5,被判定为"被踢下线"并抛出NotLoginException(场景值type = -5)。由于 Token 记录本身还保留在存储中,其 Token-Session 等关联数据也更便于后续排查或恢复。

四、顶人下线(replaced)

"顶人下线"操作发生在框架登录时顶退旧登录设备,属于框架内部操作,一般情形下你不会调用到此 API:

StpUtil.replaced(10001); // 将指定账号顶下线 StpUtil.replaced(10001, "PC"); // 将指定账号指定端顶下线 StpUtil.replacedByTokenValue("token"); // 将指定 Token 顶下线

它与踢人下线的差异点:

  1. 触发场景不同replaced通常在"同账号新设备登录、旧设备被顶退"的互斥登录场景中被框架内部调用(配合isConcurrentmaxLoginCount等配置),业务代码一般用不到;
  2. 场景值不同:顶人下线写入的标记是NotLoginException.BE_REPLACED = "-4"(源码定义),对方再次访问会提示token 已被顶下线
  3. 保留 Account-Session:从 StpLogic._logout 的注释可以看到,调用顶替下线时通常新客户端正在登录,因此不会注销该账号的 Account-Session,避免"注销后又立刻创建"造成不必要的性能浪费;而注销/踢人下线后若账号已无任何在线终端,会通过session.logoutByTerminalCountToZero()将 Account-Session 一并注销。

五、三种下线方式对比速查表

对比项强制注销 logout踢人下线 kickout顶人下线 replaced
典型使用方用户主动退出 / 后台强退后台管理踢出可疑会话框架登录时顶退旧设备
对 Token 映射的处理删除映射更新为-5标记更新为-4标记
对方再次访问的提示token 无效token 已被踢下线token 已被顶下线
NotLoginException 场景值-2(INVALID_TOKEN)-5(KICK_OUT)-4(BE_REPLACED)
是否保留 Account-Session终端清零时注销终端清零时注销保留(新客户端在登录)
框架事件回调doLogout / doBeforeLogoutdoKickout / doBeforeKickoutdoReplaced / doBeforeReplaced

场景值常量统一收敛在 NotLoginException.ABNORMAL_LIST(-1 ~ -7),业务代码可用e.getType()精确判断会话失效原因,进而区分提示语或跳转逻辑。

六、底层原理:一条 Token 下线要经历什么

三种方式最终都汇入StpLogic._logoutByTokenValue(tokenValue, logoutParameter)(源码),logoutParameter中的modeSaLogoutMode.LOGOUT / KICKOUT / REPLACED,见 SaLogoutMode 枚举)决定了后续走"删除"还是"打标记"。完整调用链如下:

  1. 前置校验:根据 token 解析 loginId(getLoginIdByTokenNotThinkFreeze),若 loginId 为空则直接返回,避免写入意外数据;同时检查isFreeze冻结状态;
  2. 发布"注销前"事件_fireBeforeLogoutEvent按 mode 触发doBeforeLogout/doBeforeKickout/doBeforeReplaced(源码),可用于在会话失效前做审计、通知等;
  3. 清理最后活跃时间:开启活跃度校验(isOpenCheckActiveTimeout)时调用clearLastActive
  4. 清理 Token-Session:未开启isKeepTokenSession时调用deleteTokenSession
  5. 处理 Token 映射并发布事件_fireLogoutEvent中按 mode 删除或篡改映射,并触发doLogout/doKickout/doReplaced(源码);
  6. 清理终端信息:从 Account-Session 移除该 Token 对应的SaTerminalInfo,若终端数为 0 则尝试注销 Account-Session。

而按loginId下线的_logout(loginId, logoutParameter)(源码)则先取出该账号的 Account-Session,遍历其终端列表,按deviceTypedeviceId过滤后,对每个命中的终端逐一执行_removeTerminal,最终走一遍上述"清理 → 改映射 → 发事件"的流程。这就是StpUtil.kickout(10001, "PC")只影响 PC 端、不影响 APP 端的原理所在。

另外,框架的门面类 StpUtil 只是把上述方法逐一定向委托给内部stpLogicStpUtil.logout(...)转发到stpLogic.logout(...)等),真实的业务逻辑全部封装在StpLogic,这也意味着多账号体系下你可以为不同loginType创建独立的StpLogic实例,实现分账号类型的精准踢人。

七、实战示例:后台踢人接口

官方在 KickoutController.java 中提供了可直接运行的演示代码(所在模块 sa-token-demo-case,启动后访问http://localhost:8081):

@RestController @RequestMapping("/kickout/") public class KickoutController { // 将指定账号强制注销 ---- http://localhost:8081/kickout/logout?userId=10001 @RequestMapping("logout") public SaResult logout(long userId) { // 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。 StpUtil.logout(userId); return SaResult.ok(); } // 将指定账号踢下线 ---- http://localhost:8081/kickout/kickout?userId=10001 @RequestMapping("kickout") public SaResult kickout(long userId) { // 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。 StpUtil.kickout(userId); return SaResult.ok(); } // 根据 Token 值踢人 ---- http://localhost:8081/kickout/kickoutByTokenValue?tokenValue=已登录账号的token值 @RequestMapping("kickoutByTokenValue") public SaResult kickoutByTokenValue(String tokenValue) { StpUtil.kickoutByTokenValue(tokenValue); return SaResult.ok(); } }

操作步骤:

  1. 先调用登录接口登录一个账号:http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
  2. 分别访问上面的logout/kickout接口,再访问登录校验接口http://localhost:8081/acc/checkLogin对比两次返回的提示信息差异——强制注销后报"Token 无效",踢人下线后报"Token 已被踢下线",直观验证两种方式的区别;
  3. 若想按 Token 精确踢人,可从登录响应中拿到 token 值,再调用kickoutByTokenValue

该行为同样有单元测试兜底:在 StpLogicLogoutTest 中,kickoutByTokenValue_marksTokenAsKicked验证了"kickoutByTokenValue 后再次 checkLogin 应抛出 NotLoginException",logoutByLoginId_clearsAccountSession验证了按 loginId 注销会清空 Token 映射与 Account-Session,可作为理解框架行为的参考依据。

八、进阶:通过注销参数精细控制

除上述基础 API 外,所有下线方法都支持传入SaLogoutParameter做精细控制(对应StpUtil.logout(loginId, SaLogoutParameter)StpUtil.kickout(loginId, SaLogoutParameter)等重载)。常用参数项(定义见 SaLogoutParameter):

参数说明
setDeviceType(String)仅注销/踢出指定设备端,null 代表全部
setDeviceId(String)仅注销/踢出指定设备 ID(更细粒度的设备定位)
setRange(SaLogoutRange)注销范围:TOKEN仅处理当前 Token,ACCOUNT级联处理账号下全部终端
setMode(SaLogoutMode)注销模式:LOGOUT/KICKOUT/REPLACED
setIsKeepTokenSession(boolean)是否保留 Token-Session 数据
setIsKeepFreezeOps(boolean)遇到冻结 Token 时是否跳过处理

例如只踢掉某账号在APP端且指定deviceId的会话,可组合deviceTypedeviceId两个条件,实现比"按端踢人"更精准的单设备下线。

九、关联文档与进一步阅读

  • 本文主题文档:踢人下线(kick.md)
  • 登录认证与会话体系:login-auth.md、session.md
  • 会话治理扩展:search-session.md(后台查询/管理会话)、mutex-login.md(互斥登录、单端登录)
  • 全局事件监听(doKickout / doReplaced 等回调的落地方式):global-listener.md

综上所述,Sa-Token 的"踢人下线"能力虽然 API 只有几行,背后却是一套"账号 → 终端 → Token 映射"的完整会话治理模型。理解deleteTokenToIdMappingupdateTokenToIdMapping的分野,就能准确判断"注销 / 踢下线 / 顶下线"三种行为对既有会话数据的不同影响,进而在后台管理中做到按需、按端、按 Token 的精细化下线控制。

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 14:13:00

论文降重与文本改写:如何避开不靠谱服务,守住查重底线

一、为什么降重这件事,越来越让人头疼? 每到毕业季,论文查重就成了悬在无数同学头上的“达摩克利斯之剑”。学校对重复率的要求越来越严格,知网、维普、格子达等查重系统的算法也在不断升级,单纯靠“换几个词”已经很…

作者头像 李华
网站建设 2026/9/13 14:12:07

GenOffice如何重构现代办公工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 14:11:57

YOLOv8-obb+TensorRT实现芯片引脚高精度实时检测

简介:本资源是一套基于YOLOv8-OBB(旋转框检测)的芯片引脚缺陷检测完整项目,面向人工智能、电子信息、自动化等专业的在校学生、教师及企业研发人员,解决高精度工业微小目标定位与缺陷识别难题,适用于毕业设…

作者头像 李华
网站建设 2026/9/13 14:11:13

Lexical 只读模式(Read Mode)与编辑模式(Edit Mode)完整指南

Lexical 只读模式(Read Mode)与编辑模式(Edit Mode)完整指南 【免费下载链接】lexical Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance. 项目地址: http…

作者头像 李华
网站建设 2026/9/13 14:09:41

Codex macOS客户端实测:从写代码到指挥智能体团队

Codex 的 macOS 客户端正式发布那天,我第一时间装上了,连着用了两周,把之前命令行版本没敢试的场景全试了一遍。先说结论:它和 GitHub Copilot、Cursor 这类工具完全不是一个物种。Copilot 是你写代码时它帮你补全,Cur…

作者头像 李华