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

相关推荐
独泪了无痕5 小时前
Spring Data Redis 快速上手
spring boot·redis·后端
wangchunyu1147 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
paopaokaka_luck8 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
Thomas.Sir8 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
程序 代码狂人8 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
2501_944521599 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
用户529589316685010 小时前
三马代驾实战:抢单池的 Redis 锁 + RabbitMQ 异步消费怎么做
redis
南归北隐10 小时前
Spring AI Alibaba Graph框架实现MCP数据库查询实战
java·后端·spring ai·mcp·spring ai mcp
古法老登瞎折腾10 小时前
大数据智能运维实战(九):让 AI 查日志——找到异常,也保留上下文
java·大数据·运维·人工智能·spring ai·mcp
ai大模型-探索者11 小时前
Function Call与MCP(区别、联系与实战)
java·服务器·网络