一、需要

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

  • 能够依据用户 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 测试登录账号 10001http://localhost:8081/kick/doLogin?userId=10001// 应用浏览器 2 测试登录账号 10002http://localhost:8081/kick/doLogin?userId=10002// 应用浏览器 3 测试登录账号 10003http://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 文档:https://sa-token.cc
  • Gitee 仓库地址:https://gitee.com/dromara/sa-token
  • GitHub 仓库地址:https://github.com/dromara/sa-token