Skip to content

现在需要你设计一个将已登录的用户踢下线的功能,如何实现? ​

  • 标签:后端、场景题

🏛 业务架构师 ​

踢下线功能的本质是登录态的版本管理 + 校验前置,我按数据模型、校验链路、触发场景三块答。

核心设计:token 版本号

Redis: user:token:version:{userId} = v
JWT payload 携带签发时的版本号

校验逻辑(网关或统一的鉴权拦截器):

java
// 1. 验签(本地)
// 2. 版本校验
if (jwt.version != redis.get("user:token:version:" + userId)) {
    return 401; // 旧token全部失效
}

踢下线 = 版本号 +1(INCR),该用户所有已签发 token 立即失效,下次请求被拒,前端收到 401 跳登录页。

分级细化:踢"某设备"还是"全部"

  • 全端踢出:全局版本号(上面方案)
  • 踢单端:版本号按设备维度 user:token:version:{userId}:{deviceId},JWT 带设备标识
  • 多端登录策略是业务配置:微信手机/PC 可同时在线、支付宝部分场景互踢——这是产品决策,架构上预留设备维度即可灵活支持

体验细节(容易被忽略但决定口碑)

  • 被踢用户收到明确提示("您的账号已在其他设备登录"),而不是莫名其妙的登录失效
  • 安全场景的踢出(风控封号)要秒级生效:网关层校验而非应用层,避免漏网路径
  • 踢出动作本身要审计(谁在什么时候踢了谁),风控场景要能回溯

触发场景梳理:改密码、注销、风控封禁、用户主动"退出所有设备"(设置页的信任功能)、会员到期。

收尾:这个功能把"JWT 无状态、无法主动失效"的短板补上了——所以我上一题说生产用"短 JWT + 版本机制"混合方案,两者是配套设计。

🏗 系统架构师 ​

踢下线本质是让已签发的凭证失效,按认证方案分两种情况设计,核心矛盾在 JWT 的"无法主动作废"。

方案一:有状态 Session(简单场景) session 存 Redis,踢人 = 删除/标记该用户的 session key,下次请求网关校验 session 不存在即 401。踢所有端就按 userId 找到他所有 session(用 Set user:{id}:sessions 维护映射)批量删。

方案二:JWT(主流,重点讲) JWT 签发后自带有效期,服务端无状态,无法直接作废。我的工程方案是黑名单/版本号:

  1. 黑名单:踢人时把该用户的 JWT 唯一标识(jti)或 userId+签发时间写入 Redis 黑名单,TTL 设为该 token 剩余有效期。网关校验签名通过后再查黑名单,命中即拒绝。成本:每次请求多一次 Redis 查询(本地缓存 1s 可扛高并发)。

  2. 版本号(推荐):用户表加 token_version 字段,JWT payload 里带签发时的版本。踢下线就 token_version + 1,旧 token 版本不匹配全部失效。优点是踢"全端"一个操作完成,且不用存每个 token;版本号缓存在用户信息里,校验零额外查询。改密码、注销全端、风控强制下线都用它。

3. 长短 token 结合:access token 15 分钟 + refresh token 7 天,踢人只需废掉 refresh token,最多 15 分钟后访问 token 过期自然掉线——把"立即生效"的代价降掉。

4. 实时性兜底:网关统一拦截校验,保证踢下线秒级生效;对在线推送场景(IM),踢下线时向长连接通道推一条 kick 消息,客户端主动断开,体验最好。

我的生产组合:版本号踢全端 + refresh token 失效 + 网关统一校验 + 长连接推送。

💻 全栈开发专家 ​

踢下线的本质是让已签发的凭证失效。按 JWT 和 Session 两种登录态给方案。

Session 方案(最简单)

会话存 Redis,踢人即删 key:DEL session:{userId},下次请求过滤器查不到即 401。

JWT 方案(重点讲)

JWT 无状态、签名无法撤销,所以引入服务端状态校验:

  1. 版本号方案(推荐):
java
// 登录
redis.set("token:ver:" + userId, uuid);  // JWT payload 里带 ver
// 每次校验
Claims c = parse(jwt);
if (!redis.get("token:ver:" + userId).equals(c.get("ver"))) {
    throw new UnauthorizedException(); // 版本不一致 = 旧 token 被踢
}
// 踢下线
redis.set("token:ver:" + userId, UUID.randomUUID().toString()); // 换版本

踢人 = 换版本号,该用户所有旧 token 立即失效。新登录写入新版本,天然实现"单设备登录"。

  1. 黑名单方案:踢出时把目标 token 的 jti 存 Redis(TTL = token 剩余有效期),校验时查黑名单。适合"只踢某台设备"。

工程细节

  • 校验逻辑放网关统一过滤器,业务无感。
  • 踢人操作要记审计日志(谁踢的、为什么),管理后台常用功能。
  • Redis 抖动时的策略要想清楚: fail-open(放行,可用性优先)还是 fail-close(拒绝,安全优先),我一般核心系统选 fail-close。