一、需求

在企业级项目中,踢人下线是一个很常见的需求,如果要设计比较完善的话,至少需要以下功能点:

  • 可以根据用户 userId 踢出指定会话,对方再次访问系统会被提示:您已被踢下线,请重新登录。
  • 可以查询出一个账号共在几个设备端登录,并返回其对应的 Token 凭证,以便后续操作。
  • 可以只踢出一个账号某一个端的会话,其他端不受影响。例如在某电商APP上可以看到当前账号共在几个手机上登录,并注销指定端的会话,当前端不受影响。

手动从零开始设计满足需求的会话架构,还是需要一定的代码量的。本篇将介绍如何使用 Sa-Token 方便的完成上述需求,

Sa-Token 框架对踢人下线做了较为完整的封装,我们可以使用极少的代码就完成踢人下线功能。

Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。

Gitee 开源地址:https://gitee.com/dromara/sa-token

首先在项目中引入 Sa-Token 依赖:

<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>

注:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

二、踢人下线 API 一览

先看看 Sa-Token 为我们提供的与踢人下线有关的API。

强制注销:

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

踢人下线:

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

强制注销 和 踢人下线 的区别在于:

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

动态图演示:

下面开始进行代码实战。

三、根据账号踢人下线

在完成踢人下线之前,我们需要先让会话完成登录。正常的登录需要根据 username + password 判断账号合法性,由于我们本篇的重点是 踢人下线

所以此处简化一下登录操作,直接填入 userId 进行登录。

package com.pj;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* 测试踢人下线
*/
@RestController
@RequestMapping("/kick/")
public class KickController { // 会话登录接口 ---- http://localhost:8081/kick/doLogin?id=10001
@RequestMapping("doLogin")
public SaResult doLogin(long userId) {
StpUtil.login(userId);
return SaResult.ok("登录成功,Token 凭证为:" + StpUtil.getTokenValue());
} // 验证当前客户端是否登录 ---- http://localhost:8081/kick/checkLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
StpUtil.checkLogin();
// 下面是登录后才会返回的数据
return SaResult.ok("您已登录成功,userId=" + StpUtil.getLoginId());
} // 根据账号Id踢人下线 ---- http://localhost:8081/kick/kickout
@RequestMapping("kickout")
public SaResult kickout(long userId) {
StpUtil.kickout(userId);
return SaResult.ok("将账号 " + userId + " 踢下线成功");
} // 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
} }

运行代码,分别用三个独立的浏览器测试登录:

// 使用浏览器 1 测试登录账号 10001
http://localhost:8081/kick/doLogin?userId=10001 // 使用浏览器 2 测试登录账号 10002
http://localhost:8081/kick/doLogin?userId=10002 // 使用浏览器 3 测试登录账号 10003
http://localhost:8081/kick/doLogin?userId=10003

之所以使用三个独立的浏览器来测试,是为了避免会话的相互覆盖,造成逻辑不可控。访问成功的话,服务端的返回信息会类似如下:

{
"code": 200,
"msg": "登录成功,Token 凭证为:f53ac098-aed4-4de2-9223-8c3f1dab656d",
"data": null
}

然后使用三个浏览器分别访问登录验证接口:

http://localhost:8081/kick/checkLogin

返回信息如下:

{
"code": 200,
"msg": "您已登录成功,userId=10001",
"data": null
}

现在开始测试踢人下线,使用任意浏览器访问:

http://localhost:8081/kick/kickout?userId=10002

返回信息:

{
"code": 200,
"msg": "将账号 10002 踢下线成功",
"data": null
}

账号 10002 将被踢下线成功,现在我们再使用浏览器2 测试一下 10002 是否仍然在线:

{
"code": 500,
"msg": "Token已被踢下线:aa5911a6-3623-4fdb-98d0-055c46353981",
"data": null
}

可以看到,10002会话已失效,无法通过登录校验。

四、根据 Token 踢人下线

业务场景举例:我要在APP上查看我的账号共在几个设备登录,并且将除我之外的设备全部踢下线。

首先我们需要在 application.yml 中添加配置:

sa-token:
is-share: false

is-share 的含义是:在多人登录同一账号时,是否共用同一个 Token:

  • 此值为 true 时,所有登录共用一个Token。
  • 此值为 false 时,每次登录新建一个Token。

在以上 KickController 的基础上,继续添加接口:

/**
* 测试踢人下线
*/
@RestController
@RequestMapping("/kick/")
public class KickController { // 其他代码... // 以下是需要新添加的代码 // 查询我的账号已经在几个设备登录 ---- http://localhost:8081/kick/tokenList
@RequestMapping("tokenList")
public SaResult tokenList() {
long currUserId = StpUtil.getLoginIdAsLong();
List<String> tokenList = StpUtil.getTokenValueListByLoginId(currUserId);
return SaResult.data(tokenList);
} // 根据 Token 踢人下线 ---- http://localhost:8081/kick/kickoutToken?token=xxxx
@RequestMapping("kickoutToken")
public SaResult kickoutToken(String token) {
StpUtil.kickoutByTokenValue(token);
return SaResult.ok("将Token: " + token + " 踢下线成功");
} }

重启项目(如果集成 Redis 了就清空 Redis数据一下),分别从三个独立的浏览器测试访问:

http://localhost:8081/kick/doLogin?userId=10001

返回如下:

{
"code": 200,
"msg": "登录成功,Token 凭证为:450b8b73-f52d-4496-b67e-bdd579c8708a",
"data": null
}

仔细观察三个浏览器返回的信息,虽然三个浏览器都是登录账号 10001,但是每次返回的 Token 凭证都是不一样的。

现在查询一下当前账号一共在几个设备完成了登录:

http://localhost:8081/kick/tokenList

返回如下:

{
"code": 200,
"msg": "ok",
"data": [
"450b8b73-f52d-4496-b67e-bdd579c8708a",
"39d7974b-327d-4aea-a0b7-d90ab47caf0c",
"d73c1bc5-d04f-4dc2-81ee-42c9438f9d78"
]
}

现在选一个 Token,将其踢下线:

http://localhost:8081/kick/kickoutToken?token=d73c1bc5-d04f-4dc2-81ee-42c9438f9d78

返回信息如下:

{
"code": 200,
"msg": "将Token: d73c1bc5-d04f-4dc2-81ee-42c9438f9d78 踢下线成功",
"data": null
}

然后在对应的浏览器,验证一下登录状态:

http://localhost:8081/kick/checkLogin

返回如下:

{
"code": 500,
"msg": "Token已被踢下线:d73c1bc5-d04f-4dc2-81ee-42c9438f9d78",
"data": null
}

可以看到,该设备登录的会话已被踢下线。那么同账号的其他设备有没有受到影响呢,我们从其他浏览器验证一下:

http://localhost:8081/kick/checkLogin

返回如下:

{
"code": 200,
"msg": "您已登录成功,userId=10001",
"data": null
}

可以看到,只有踢出的 Token 被强制下线了,其他端并没有受到影响。


参考资料

使用 Sa-Token 完成踢人下线功能的更多相关文章

  1. Spring Security框架中踢人下线技术探索

    1.背景 在某次项目的开发中,使用到了Spring Security权限框架进行后端权限开发的权限校验,底层集成Spring Session组件,非常方便的集成Redis进行分布式Session的会话 ...

  2. Android学习之基础知识八—Android广播机制实践(实现强制下线功能)

    强制下线功能算是比较常见的了,很多的应用程序都具备这个功能,比如你的QQ号在别处登录了,就会将你强制挤下线.实现强制下线功能的思路比较简单,只需要在界面上弹出一个对话框,让用户无法进行任何操作,必须要 ...

  3. android: 实现强制下线功能

    强制下线功能应该算是比较常见的了,很多的应用程序都具备这个功能,比如你的 QQ 号在别处登录了,就会将你强制挤下线.其实实现强制下线功能的思路也比较简单,只需要 在界面上弹出一个对话框,让用户无法进行 ...

  4. GS踢玩家下线功能

    GS踢玩家下线功能 //key:userId, val:nChannelId (当前在线用户) std::map<int, int> m_mapOnLineUserByUid; ///&l ...

  5. Android学习总结(八)———— 广播的最佳实践(实现强制下线功能)

    一.基本概念 强制下线功能功能应该算是比较常见的了,很多应用程序都具备这个功能,比如你的QQ号或者微信号在别处登录了,就会将你强制挤下线.只需要在界面上弹出一个对话框,让用户无法进行任何其他的操作,必 ...

  6. android#boardcast#广播实现强制下线功能

    参考自<第一行代码>——郭霖 强制下线功能需要先关闭掉所有的活动(Activity),然后回到登录界面.先创建一个ActivityCollector类用于管理所有的活动,代码如下所示: p ...

  7. Android广播时间——实现强制下线功能

    目录 思路:强制下线功能需要先关闭掉所有的活动,然后回到登录界面. 步骤 1.关闭所有活动 2.创建BaseActivity类作为所有活动的父类,因为需要用ActivityCollector管理所有活 ...

  8. java中如何踢人下线?封禁某个账号后使其会话立即掉线!

    需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...

  9. Android学习总结——强制下线功能(广播)

    最近一口气买了好几本书,其中Android的<第一行代码>觉得真心不错,学到这个内容,顺便做个总结,加深印象. 强制下线的基本思想就是在界面上弹出一个对话框,让用户必须点击确定按钮跳转到登 ...

  10. 利用创建的sa token来创建kubectl的config文件

    1.第一步 创建一sa,并授予需要的一个权限(需要授予的权限) 2.第二步 取步骤1中的sa的 secret的token文件并进行base64解码      echo "$TOKEN&quo ...

随机推荐

  1. 字符串练习2 最长抑或路径(01trie树)

    题目链接在这里:P4551 最长异或路径 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 是一道比较经典的问题,对于异或问题经常会使用01trie树来解决. 当然01trie树只是用 ...

  2. 转发:前端组件化之Monorepo方案实战

    前言 在上一篇的前端组件化方案探究中,我们研究了什么是组件化以及我们为什么需要组件化.也调研和测试了一些开源项目,并且在使用.学习.研究.对比之后最终确定了以 pnpm + workspace + c ...

  3. CentOS 7 时区设置 EST和CST设置

    1. https://blog.csdn.net/allway2/article/details/102995747 CentOS 7 时区设置# timedatectl status      Lo ...

  4. Flask CURD(增删改查)

    1.创建flask项目 2.修改配置文件: ''' config.py 保存项目配置 ''' 导入Flask模块 from flask import Flask 额外安装: 数据库操作模块 from ...

  5. StyleGAN 调整面部表情,让虚拟人脸更生动

    目录 人脸表情 调整步骤 调整结果 人脸表情 通过上一篇文章 StyleGAN 生成的人脸:https://www.cnblogs.com/tinygeeker/p/17236264.html 人脸图 ...

  6. MySQL 索引的种类

    我们知道一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重. 一.平衡多路查 ...

  7. PHP 图片的合并,微信小程序码合并,文字合并

    //业务需求:我们需要一个微信小程序码,但是是需要提供给别人扫码的但是只有一个纯粹的小程序码是不好看的,所以需要推广的海报图片.再结合文字 最终效果 准备工作  1.需要海报的底图  2.小程序码的图 ...

  8. ChatGPT3.5使用体验

    优点 1.ChatGPT 能颠覆现有的搜索引擎(百度.谷歌). 2.ChatGPT 的交互体验非常好,满足"智能助手"这种工具. 3.如何使用好ChatCPT? 回到一个经典的问题 ...

  9. Rancher 系列文章-Rancher 对接 Active Directory 实战

    概述 只要是个公司,基本上都有邮箱和 AD(Active Directory). 在 AD 里,已经有了: 用户 账号密码 邮箱 用户组 组织架构 所以对于一些仅限于本公司一定范围内人员使用的管理或后 ...

  10. Ubuntu+uWSGI部署基于Django的API【鸿篇巨制,事无巨细】

    背景 任务: 视频翻译项目需要在两个服务器上进行通信(国内&海外的阿里服务器). 因为python是主语言,选用了Django 来快速部署API. 注:Django中文文档:https://d ...