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 负责"证明身份",载荷里塞 userId、tenantId、terminal(端标识)、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 里带上 terminal 和 jti:

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

相关推荐
MandalaO_O2 小时前
IDEA 开发(快捷键 + 调试 + 序列化)
java·ide·intellij-idea
xiaoqiMikko3 小时前
JVM 线上排查实战(七):jps 看不见它、jstack 连不上它,可它明明活得好好的
java·jvm
狼爷4 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
念何架构之路4 小时前
zap扩展生态与总结
java·前端·数据库
第七页独白5 小时前
汽车零件厂如何通过 QMS 真正落地 IATF 16949——QMS软件系统:品质检验-内审稽核-8d客诉管理:全星质量管理软件系统
java·前端·数据库
QCoding5 小时前
Spring AI Alibaba Graph实战:从ReAct Agent到Workflow,企业AI复杂流程该如何编排?
java·人工智能
用户094248568035 小时前
第25章:Java虚拟线程(Project Loom)实战与调度协作
java·jvm
砚底藏山河5 小时前
量化实战:截面因子有效性检验(IC 分析与分层回测)
java·python·金融·maven
wuminyu5 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++
Elastic 中国社区官方博客5 小时前
使用 Lucene 搜索你的 Bean — 索引
java·大数据·数据库·elasticsearch·搜索引擎·全文检索·lucene