Java使用Sa-Token框架完成踢人下線功能
一、需求
在企業(yè)級(jí)項(xiàng)目中,踢人下線是一個(gè)很常見(jiàn)的需求,如果要設(shè)計(jì)比較完善的話,至少需要以下功能點(diǎn):
- 可以根據(jù)用戶 userId 踢出指定會(huì)話,對(duì)方再次訪問(wèn)系統(tǒng)會(huì)被提示:您已被踢下線,請(qǐng)重新登錄。
- 可以查詢出一個(gè)賬號(hào)共在幾個(gè)設(shè)備端登錄,并返回其對(duì)應(yīng)的 Token 憑證,以便后續(xù)操作。
- 可以只踢出一個(gè)賬號(hào)某一個(gè)端的會(huì)話,其他端不受影響。例如在某電商APP上可以看到當(dāng)前賬號(hào)共在幾個(gè)手機(jī)上登錄,并注銷(xiāo)指定端的會(huì)話,當(dāng)前端不受影響。
手動(dòng)從零開(kāi)始設(shè)計(jì)滿足需求的會(huì)話架構(gòu),還是需要一定的代碼量的。本篇將介紹如何使用 Sa-Token 方便的完成上述需求,Sa-Token 框架對(duì)踢人下線做了較為完整的封裝,我們可以使用極少的代碼就完成踢人下線功能。
Sa-Token 是一個(gè)輕量級(jí) java 權(quán)限認(rèn)證框架,主要解決登錄認(rèn)證、權(quán)限認(rèn)證、單點(diǎn)登錄、OAuth2、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問(wèn)題。Gitee 開(kāi)源地址:gitee.com/dromara/sa-token
首先在項(xiàng)目中引入 Sa-Token 依賴:
<!-- Sa-Token 權(quán)限認(rèn)證 -->
<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 為我們提供的與踢人下線有關(guān)的API。
強(qiáng)制注銷(xiāo):
StpUtil.logout(10001); // 強(qiáng)制指定賬號(hào)注銷(xiāo)下線
StpUtil.logout(10001, "PC"); // 強(qiáng)制指定賬號(hào)指定端注銷(xiāo)下線
StpUtil.logoutByTokenValue("token"); // 強(qiáng)制指定 Token 注銷(xiāo)下線 踢人下線:
StpUtil.kickout(10001); // 將指定賬號(hào)踢下線
StpUtil.kickout(10001, "PC"); // 將指定賬號(hào)指定端踢下線
StpUtil.kickoutByTokenValue("token"); // 將指定 Token 踢下線強(qiáng)制注銷(xiāo) 和 踢人下線 的區(qū)別在于:
- 強(qiáng)制注銷(xiāo)等價(jià)于對(duì)方主動(dòng)調(diào)用了注銷(xiāo)方法,再次訪問(wèn)會(huì)提示:Token無(wú)效。
- 踢人下線不會(huì)清除Token信息,而是將其打上特定標(biāo)記,再次訪問(wèn)會(huì)提示:Token已被踢下線。
動(dòng)態(tài)圖演示:

下面開(kāi)始進(jìn)行代碼實(shí)戰(zhàn)。
三、根據(jù)賬號(hào)踢人下線
在完成踢人下線之前,我們需要先讓會(huì)話完成登錄。正常的登錄需要根據(jù) username + password 判斷賬號(hào)合法性,由于我們本篇的重點(diǎn)是 踢人下線,所以此處簡(jiǎn)化一下登錄操作,直接填入 userId 進(jìn)行登錄。
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;
/**
* 測(cè)試踢人下線
*/
@RestController
@RequestMapping("/kick/")
public class KickController {
// 會(huì)話登錄接口 ---- http://localhost:8081/kick/doLogin?id=10001
@RequestMapping("doLogin")
public SaResult doLogin(long userId) {
StpUtil.login(userId);
return SaResult.ok("登錄成功,Token 憑證為:" + StpUtil.getTokenValue());
}
// 驗(yàn)證當(dāng)前客戶端是否登錄 ---- http://localhost:8081/kick/checkLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
StpUtil.checkLogin();
// 下面是登錄后才會(huì)返回的數(shù)據(jù)
return SaResult.ok("您已登錄成功,userId=" + StpUtil.getLoginId());
}
// 根據(jù)賬號(hào)Id踢人下線 ---- http://localhost:8081/kick/kickout
@RequestMapping("kickout")
public SaResult kickout(long userId) {
StpUtil.kickout(userId);
return SaResult.ok("將賬號(hào) " + userId + " 踢下線成功");
}
// 全局異常攔截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}運(yùn)行代碼,分別用三個(gè)獨(dú)立的瀏覽器測(cè)試登錄:
// 使用瀏覽器 1 測(cè)試登錄賬號(hào) 10001 http://localhost:8081/kick/doLogin?userId=10001 // 使用瀏覽器 2 測(cè)試登錄賬號(hào) 10002 http://localhost:8081/kick/doLogin?userId=10002 // 使用瀏覽器 3 測(cè)試登錄賬號(hào) 10003 http://localhost:8081/kick/doLogin?userId=10003
之所以使用三個(gè)獨(dú)立的瀏覽器來(lái)測(cè)試,是為了避免會(huì)話的相互覆蓋,造成邏輯不可控。訪問(wèn)成功的話,服務(wù)端的返回信息會(huì)類(lèi)似如下:
{
"code": 200,
"msg": "登錄成功,Token 憑證為:f53ac098-aed4-4de2-9223-8c3f1dab656d",
"data": null
}然后使用三個(gè)瀏覽器分別訪問(wèn)登錄驗(yàn)證接口:http://localhost:8081/kick/checkLogin復(fù)制代碼
返回信息如下:
{
"code": 200,
"msg": "您已登錄成功,userId=10001",
"data": null
}現(xiàn)在開(kāi)始測(cè)試踢人下線,使用任意瀏覽器訪問(wèn):http://localhost:8081/kick/kickout?userId=10002復(fù)制代碼
返回信息:
{
"code": 200,
"msg": "將賬號(hào) 10002 踢下線成功",
"data": null
}賬號(hào) 10002 將被踢下線成功,現(xiàn)在我們?cè)偈褂脼g覽器2 測(cè)試一下 10002 是否仍然在線:
{
"code": 500,
"msg": "Token已被踢下線:aa5911a6-3623-4fdb-98d0-055c46353981",
"data": null
}可以看到,10002會(huì)話已失效,無(wú)法通過(guò)登錄校驗(yàn)。
四、根據(jù) Token 踢人下線
業(yè)務(wù)場(chǎng)景舉例:我要在APP上查看我的賬號(hào)共在幾個(gè)設(shè)備登錄,并且將除我之外的設(shè)備全部踢下線。
首先我們需要在 application.yml 中添加配置:
sa-token:
is-share: falseis-share 的含義是:在多人登錄同一賬號(hào)時(shí),是否共用同一個(gè) Token:
- 此值為 true 時(shí),所有登錄共用一個(gè)Token。
- 此值為 false 時(shí),每次登錄新建一個(gè)Token。
在以上 KickController 的基礎(chǔ)上,繼續(xù)添加接口:
/**
* 測(cè)試踢人下線
*/
@RestController
@RequestMapping("/kick/")
public class KickController {
// 其他代碼...
// 以下是需要新添加的代碼
// 查詢我的賬號(hào)已經(jīng)在幾個(gè)設(shè)備登錄 ---- http://localhost:8081/kick/tokenList
@RequestMapping("tokenList")
public SaResult tokenList() {
long currUserId = StpUtil.getLoginIdAsLong();
List<String> tokenList = StpUtil.getTokenValueListByLoginId(currUserId);
return SaResult.data(tokenList);
}
// 根據(jù) Token 踢人下線 ---- http://localhost:8081/kick/kickoutToken?token=xxxx
@RequestMapping("kickoutToken")
public SaResult kickoutToken(String token) {
StpUtil.kickoutByTokenValue(token);
return SaResult.ok("將Token: " + token + " 踢下線成功");
}
}重啟項(xiàng)目(如果集成 Redis 了就清空 Redis數(shù)據(jù)一下),分別從三個(gè)獨(dú)立的瀏覽器測(cè)試訪問(wèn):http://localhost:8081/kick/doLogin?userId=10001復(fù)制代碼
返回如下:
{
"code": 200,
"msg": "登錄成功,Token 憑證為:450b8b73-f52d-4496-b67e-bdd579c8708a",
"data": null
}仔細(xì)觀察三個(gè)瀏覽器返回的信息,雖然三個(gè)瀏覽器都是登錄賬號(hào) 10001,但是每次返回的 Token 憑證都是不一樣的。
現(xiàn)在查詢一下當(dāng)前賬號(hào)一共在幾個(gè)設(shè)備完成了登錄:http://localhost:8081/kick/tokenList復(fù)制代碼
返回如下:
{
"code": 200,
"msg": "ok",
"data": [
"450b8b73-f52d-4496-b67e-bdd579c8708a",
"39d7974b-327d-4aea-a0b7-d90ab47caf0c",
"d73c1bc5-d04f-4dc2-81ee-42c9438f9d78"
]
}現(xiàn)在選一個(gè) Token,將其踢下線:http://localhost:8081/kick/kickoutToken?token=d73c1bc5-d04f-4dc2-81ee-42c9438f9d78復(fù)制代碼
返回信息如下:
{
"code": 200,
"msg": "將Token: d73c1bc5-d04f-4dc2-81ee-42c9438f9d78 踢下線成功",
"data": null
}然后在對(duì)應(yīng)的瀏覽器,驗(yàn)證一下登錄狀態(tài):http://localhost:8081/kick/checkLogin復(fù)制代碼
返回如下:
{
"code": 500,
"msg": "Token已被踢下線:d73c1bc5-d04f-4dc2-81ee-42c9438f9d78",
"data": null
}可以看到,該設(shè)備登錄的會(huì)話已被踢下線。那么同賬號(hào)的其他設(shè)備有沒(méi)有受到影響呢,我們從其他瀏覽器驗(yàn)證一下:http://localhost:8081/kick/checkLogin復(fù)制代碼
返回如下:
{
"code": 200,
"msg": "您已登錄成功,userId=10001",
"data": null
}可以看到,只有踢出的 Token 被強(qiáng)制下線了,其他端并沒(méi)有受到影響。
參考資料
- Sa-Token 文檔:sa-token.cc
- Gitee 倉(cāng)庫(kù)地址:gitee.com/dromara/sa-token
- GitHub 倉(cāng)庫(kù)地址:github.com/dromara/sa-token
到此這篇關(guān)于Java使用Sa-Token框架完成踢人下線功能的文章就介紹到這了,更多相關(guān)Sa-Token 踢人下線內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于StringUtils.isBlank()的使用及說(shuō)明
這篇文章主要介紹了關(guān)于StringUtils.isBlank()的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Java實(shí)現(xiàn)二叉樹(shù)的示例代碼(遞歸&迭代)
二叉樹(shù)(Binary?tree)是樹(shù)形結(jié)構(gòu)的一個(gè)重要類(lèi)型。本文將利用Java語(yǔ)言實(shí)現(xiàn)二叉樹(shù),文中的示例代碼講解詳細(xì),需要的同學(xué)可以參考一下2022-03-03
java如何更改數(shù)據(jù)庫(kù)中的數(shù)據(jù)
這篇文章主要介紹了java如何更改數(shù)據(jù)庫(kù)中的數(shù)據(jù),修改數(shù)據(jù)庫(kù)是數(shù)據(jù)庫(kù)操作必不可少的一部分,使用Statement接口中的excuteUpdate()方法可以修改數(shù)據(jù)表中的數(shù)據(jù),感興趣的朋友跟隨小編一起看看吧2021-11-11
從零開(kāi)始Java實(shí)現(xiàn)Parser?Combinator
這篇文章主要為大家介紹了從零開(kāi)始Java實(shí)現(xiàn)Parser?Combinator過(guò)程及原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Java中多個(gè)線程交替循環(huán)執(zhí)行的實(shí)現(xiàn)
有些時(shí)候面試官經(jīng)常會(huì)問(wèn),兩個(gè)線程怎么交替執(zhí)行呀,本文就來(lái)詳細(xì)的介紹一下Java中多個(gè)線程交替循環(huán)執(zhí)行的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
使用JAVA實(shí)現(xiàn)高并發(fā)無(wú)鎖數(shù)據(jù)庫(kù)操作步驟分享
一個(gè)在線2k的游戲,每秒鐘并發(fā)都嚇?biāo)廊恕鹘y(tǒng)的hibernate直接插庫(kù)基本上是不可行的。我就一步步推導(dǎo)出一個(gè)無(wú)鎖的數(shù)據(jù)庫(kù)操作,詳情看下文2013-11-11

