java实现踢下线用户_浅谈踢人下线的设计思路!(附代码实现方案)

前言

前两天写了一篇文章,主要讲了下java中如何实现踢人下线,原文连接:java中如何踢人下线?封禁某个帐号后使其会话当即掉线!前端

原本只是简单阐述一下踢人下线的业务场景和实现方案,没想到引出那么多大佬把小弟喷的睁不开眼睛,为了不你们继续喷我,特再写下此篇文章,完全讲清楚各类场景下踢人下线的设计思路,若有不足之处还请各位大佬轻喷!java

好了废话很少说,正文开始算法

正文

若是把踢人下线比喻成拆房子,那么在学会拆房以前,咱们必需要了解这座房子是怎么盖起来的,不一样的盖法对应不一样的拆法,不能混为一谈spring

对于目前大多数系统来说,登陆主要有两种方式,一是传统Session模式,二是jwt令牌模式数据库

传统Session模式

咱们先以Session模式为例,这种模式是怎么登陆的呢?服务器

(注:此处的Session不单指HttpSession,指一切使用服务端控制会话的手段)session

这里咱们不使用任何框架,从底层逻辑开始提及。app

首先,你须要一个全局拦截器,拦截全部会话请求,若是此会话已经登陆,那么拦截器放行,若是未登陆,直接将此会话强制重定向到登陆接口框架

在登陆接口,咱们须要接受两个参数:username + password, 拿这两个参数去数据库中获取数据

若是查不到数据,直接返回用户名或密码错误,若是能够查找到数据,那么开始登陆

利用必定的算法(例如uuid),生成一个随机字符串,就像这样子:623368f0-ae5e-4475-a53f-93e4225f16ae, 这就是咱们的token

如今咱们须要作两件事,一是创建此token与UserId的映射关系,二是把这个token返回给前端

创建映射:在Redis中添加一条数据,假如userId=10001,那么咱们须要RedisUtil.set("623368f0-ae5e-4475-a53f-93e4225f16ae", 10001)

将token传递给前台,你能够放到Cookie里,或者直接放到返回体body里

大工告成,会话登陆完毕!在全局拦截器里,咱们不认userId只认token,谁持有623368f0-ae5e-4475-a53f-93e4225f16ae这个令牌,谁就是用户10001!

一个会话访问进来,有token且token有效,那么会话放行!没有?乖乖滚去登陆!

此时不难看出,一个客户端要保持会话登陆的两个必要条件:spring-boot

此客户端持有token

这个token是一个有效token,即:能够从Redis中找到对应的UserId

而咱们要作踢人下线,就必须从这两点至少选择其一开始下手。

首先咱们先明确一点:除非客户端主动注销,不然咱们是没法清除一个已经颁发到客户端的token的。

(除了Cookie清除技术和WebSocket实时推送技术能够作到,可是这两种技术都须要客户端主动配合,咱们如今的假设是客户端拒不配合,咱们须要将它强制清退下线。)

如今,咱们只能从第二点下手,即:清除此token与UserId的映射关系

你可能会想,这不简单?Redis清除一个键值,还不是一行代码就能解决的事情?

此时你可能漏掉了关键的一点,那就是,咱们只在Redis中存储了token -> UserId的映射关系,若是咱们要踢出用户10001,正常状况下,咱们没法只根据10001找到它对应的token是哪一个键值

要解决这个问题,咱们就必须把UserId -> token的映射关系也存储一份,你能够存储在数据库中,也能够存储在Redis中,为了性能考虑,咱们使用Redis

如今事情变得简单起来,要踢人下线,咱们只须要两步:

找到帐号10001对应的token键值

删除这个键值

OK,踢出成功,待到此帐号下一次访问系统时,虽然他携带了token,可是此token已成为无效token,乖乖去登录吧!

此时你可能会说:

就这?我建立个集合保存全部要踢出下线的帐号,每次拦截器里判断这个会话是否在这个集合中不就OK了?

大佬请慢喷!这就是我要说的第二种模式————黑名单机制,且往下看

jwt模式

jwt模式的登录步骤与传统Session模式区别不大,在此暂不赘述

不一样点在于,jwt登录时,不会在服务器保存任何会话信息,全部的用户参数都被写进了jwt生成的token中

(因此jwt的token才会长的那么长!一般两三百字符长度起步)

一个会话是否有效,只看这个会话携带的token能不能正常解析出数据!

这也就意味着令牌的合法性是令牌自解释的,而不是服务器说了算!

因此,相比于传统Session模式,jwt对令牌的可控性就弱了不少,没法作到主动清除token -> UserId 映射关系的操做

除非你手动更换jwt令牌生成的算法秘钥,可是这样会形成系统中全部令牌所有失效,所有用户集体下线!这是万万不行的。

那怎么办?难道我就不能作到踢人下线的操做吗?

其实办法确定是有的,只要思想不滑坡,方法总比困难多!

那就是利用黑名单机制:咱们要踢出哪一个用户,只须要将他的UserId或者jwt-token放进一个黑名单里,而后咱们在拦截器里检查每一个请求的token或者UserId是否存在于这个黑名单里便可!

这种方式和传统Session模式孰优孰劣呢?只能说各有千秋!

黑名单机制在存储时节省性能,在拦截器里多了一步黑名单检测的步骤,浪费性能!

不过坦白了讲,这丁点的性能的浪费对于如今的CPU来讲都是毛毛雨,能够直接忽略!

题外话

在我一位同事的项目中,给我提供了jwt踢人下线的另外一种实现思路:

那就是在生成jwt令牌时,加入一个固定的参数当作令牌生成因子,若是要将一个用户踢出下线,只须要修改一下这个因子的值,而后在拦截器里每次校验这个因子生成的令牌是否与客户端传递的令牌一致!便可判断出这个token是否已被拉黑!

这种模式提供了一个比较新颖的逻辑算法,可是严格来说,仍是借助服务器存储必定的数据完成的会话验证,仍然属于Session模式。在此暂不展开细讲。

代码实现方案?

说了这么多理论,总归是要上代码的,因为笔者除了sa-token框架之外没有找到任何一个框架对踢人下线有直接现成的解决方案,因此在此暂以sa-token框架为例

首先添加pom.xml依赖

cn.dev33

sa-token-spring-boot-starter

1.12.1

在用户登陆时将帐号id写入会话中

@RestController

@RequestMapping("user")

public class UserController {

@RequestMapping("doLogin")

public String doLogin(String username, String password) {

// 此处仅做示例模拟,真实项目须要从数据库中查询数据进行比对

if("zhang".equals(username) && "123456".equals(password)) {

StpUtil.setLoginId(10001);

return "登陆成功";

}

return "登陆失败";

}

}

将指定id的帐号踢出在线

// 使指定id帐号的会话注销登陆,对方再次访问系统时会抛出`NotLoginException`异常,场景值为-5

@RequestMapping("kickout")

public String kickout(long userId) {

StpUtil.logoutByLoginId(userId);

return "踢出成功";

}

后话

文章写的再详细也不免会有遗漏之处,在此还求你们轻喷,能够在评论出留言指出不足之处

若是以为文章写得不错还请你们不要吝惜为文章点个赞,您的支持是我更新的最大动力!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值