Spring Boot 3.5 脚手架里的 JWT + Redis 双轨会话,双端 token 隔离我是这么设计的

在这里插入代码片# Spring Boot 3.5 脚手架里的 JWT + Redis 双轨会话,双端 token 隔离我是这么设计的

起因

公司要把手里几个项目沉淀成一套脚手架,第一件事就是把登录鉴权重新捋一遍。管理后台、小程序、H5 三端共用同一个后端,最怕的就是 token 串端------管理端登录的 token 拿去调小程序接口居然也能通,这种事故出过一次就够呛。当时排查半天,发现就是后端对"这 token 是从哪个端来的"完全没有概念,谁都能调谁的接口。

所以这次做脚手架(qkl-boot),鉴权这块我直接按"双端隔离 + 双轨会话"来设计。

为什么是 JWT + Redis 双轨

纯 JWT 的好处是无状态,服务随便横向扩,但代价是没法主动踢人。员工离职了、用户改密码了,token 在过期前照样能用,这不合适。所以我的做法是:

  • JWT 负责"证明身份",载荷里塞 userIdtenantIdterminal(端标识)、jti(会话唯一 ID)
  • Redis 负责"会话生命周期",登录、登出、踢人、改密都是操作 Redis

JWT 签发核心代码就这一段:

java 复制代码
public String createAccessToken(Long userId, String tenantId, TerminalTypeEnum terminal, String jti) {
    long expire = properties.getToken().getAccessExpireSecond();
    Date now = new Date();
    Date exp = new Date(now.getTime() + expire * 1000);
    return Jwts.builder()
            .id(jti)
            .claim("userId", userId)
            .claim("tenantId", tenantId)
            .claim("terminal", terminal.name())
            .issuedAt(now)
            .expiration(exp)
            .signWith(secretKey())
            .compact();
}

注意那个 terminal,这是双端隔离的关键。每次请求进来先解析 JWT,看 terminal 是 ADMIN 还是 MOBILE,再决定这个接口允不允许访问。管理端接口只认 ADMIN 的 token,天然就把串端问题挡在这一层。

Access + Refresh 双轨

access token 有效期短(默认几十分钟),refresh token 有效期长(天级)。但这里有个细节我踩过坑:管理端和移动端的 refresh 时长不能一样。管理员可能连着几天用后台,refresh 给长一点;移动端用户量大、安全要求高,refresh 短一点,逼着用户定期重新登录。

java 复制代码
long refreshTtl = user.getTerminal() == TerminalTypeEnum.ADMIN
        ? properties.getToken().getAdminRefreshExpireSecond()
        : properties.getToken().getMobileRefreshExpireSecond();

Redis 会话隔离

签发 token 时同时往 Redis 写一份会话,key 里带上 terminaljti

java 复制代码
String loginKey = loginKey(user.getTerminal(), jti);
Map<String, Object> session = new HashMap<>();
session.put("userId", user.getUserId());
session.put("tenantId", user.getTenantId());
session.put("username", user.getUsername());
// ...
redisTemplate.opsForValue().set(loginKey, JSONUtil.toJsonStr(session), Duration.ofSeconds(refreshTtl));

登出就是删这个 key,踢人就是删这个 key,改密就是把所有 jti 的 key 全扫出来删掉。jti 每次 refresh 都会换新的,旧的 access 即使没过期,因为 Redis 里对应的 session 没了,也照样失效。

踩坑记录

  1. JWT 密钥太短 :密钥长度少于 32 字节直接抛异常,别用 "123456" 这种去签名,HMAC 密钥太短等于裸奔。我在 JwtService 里加了硬校验,密钥不够直接拒绝,而不是等线上出了安全事件再补。
  2. 时钟偏移 :解析 token 的时候加 clockSkewSeconds(30)。服务器时钟和签发端时钟稍微差一点,不带这个,明明没过期的 token 会随机报"已过期",排查起来特别玄学。
  3. Redis 挂了对鉴权的影响:鉴权依赖 Redis,Redis 挂了要能快速失败而不是拖死整个服务,Redis 连接池超时参数调小,快速报错比卡死好排查。

后台的工作台和用户管理长这样:

总结

双轨会话 + Redis 会话隔离这套组合,本质就是用 JWT 的"快"配合 Redis 的"可控"。核心就三点:载荷带 terminal 做端隔离、jti 做会话指纹、Redis 做生命周期管理。脚手架源码在 qkl-boot 仓库里,配套的 uniapp 移动端在 job-uniapp,有需要可以直接参考。

项目源码:https://gitee.com/gzqkl/qkl-boot · https://gitee.com/gzqkl/job-uniapp

相关推荐
IT枫斗者枫哥1 小时前
MyBatis 列表查询优化:一页20条数据,21次SQL改成2次
java
凤山老林1 小时前
RocketMQ 5.0 实战避坑与调优:事务、延迟、轨迹及高可用落地指南
spring boot·rocketmq
君顾11 小时前
本地电竞服务交易系统架构设计与实战:从同城服务撮合到订单履约
java·开发语言·电竞
TanYYF1 小时前
Spring Boot 2 与 Spring Boot 3 自动装配的区别及 Boot 3 装配流程详解
spring boot
小刘在重生~1 小时前
十六(3)、《集合扩充》CopyOnWriteArrayList 超详细解析(线程安全集合)
java·笔记·面试·职场和发展
逆境不可逃1 小时前
Pi Agent 学习笔记:对话怎样保存、分支与恢复
java
RuoyiOffice1 小时前
SpringBoot3+OnlyOffice 自动保存版本:定时回存、间隔合并与 50 版上限怎么配
spring boot·在线文档·onlyoffice·版本管理·spring boot 3·自动保存·ruoyi office
huaweichenai1 小时前
spring boot使用hutool实现调用外部接口
java·spring boot
泡海椒2 小时前
Java 后端最优 PDF 导出方案:jquick-pdf 项目引入与快速测试
java·开发语言·pdf