• 使用 Sa-Token 完成踢人下线功能


    一、需求

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

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

    手动从零开始设计满足需求的会话架构,还是需要一定的代码量的。本篇将介绍如何使用 Sa-Token 方便的完成上述需求,
    Sa-Token 框架对踢人下线做了较为完整的封装,我们可以使用极少的代码就完成踢人下线功能。

    Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
    Gitee 开源地址:https://gitee.com/dromara/sa-token

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

    
    
        cn.dev33
        sa-token-spring-boot-starter
        1.34.0
    
    

    注:如果你使用的是 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 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 被强制下线了,其他端并没有受到影响。


    参考资料

  • 相关阅读:
    TrajGAT:轨迹相似度计算模型
    python自动化第一篇—— 带图文的execl的自动化合并
    tp6教程
    掌握逻辑漏洞复现技术,保护您的数字环境
    HadSky+内网穿透打造个人专属社区论坛并远程访问
    算法必刷系列之位运算
    java中内存泄漏和内存溢出指什么呢?
    Linux文件系统、文件I/O和动静态库
    Linux命令lsscsi详解
    FreeCAD二次开发-基于PyQT对话框与FC交互的开发
  • 原文地址:https://www.cnblogs.com/shengzhang/p/17384468.html