资讯详情

Spring Boot统一登录实战:认证中心、双Token与Redis登录态设计

📅 2026/10/31 3:21:03 | 华诺云谱 👁 阅读
Spring Boot统一登录实战:认证中心、双Token与Redis登录态设计
1. 从三套账号到一套凭证统一登录到底在解决什么前两年接手一个内部平台整合的活儿用户侧最刺耳的反馈不是功能不好用而是“我在 A 系统刚登录完切到 B 系统又要输一遍密码账号还是另外一套”。这类问题在系统数量超过三个之后几乎必然出现有的系统用手机号登录有的用员工号有的还留着最早那套 admin/admin 的默认账号。等到要做权限审计、要看某个人到底能进哪些系统、要在他离职当天把所有入口关掉麻烦才真正开始。所以后来我们把 Spring Boot 统一登录这件事重新做了一遍目标很明确——所有业务系统不再自己管密码认证只在一处发生凭证能在系统之间被信任。这里说的Spring Boot 统一登录指的是把“用户身份确认”这件事从各个业务系统里抽出来独立成一个认证中心业务系统只负责“拿到凭证之后判断这个人能不能干这件事”。它和单点登录不完全是一回事单点登录强调的是“登录一次多处通行”的体验统一登录强调的是“账号体系、认证逻辑、登录态只有一份”的治理能力。前者是结果后者是手段。很多团队一上来就奔着 SSO 的体验去做做完了发现账号还是分散的密码策略还是各管各的等于没解决根本问题。这篇文章面向的是后端开发、架构设计以及那些正在被“第 N 个系统要接登录”折磨的技术负责人。不管你是要做一个全新的认证中心还是想把现有的几套账号收编到一处下面这些设计取舍、代码结构、上线后踩到的坑都是我实际趟过的。阅读门槛不高会写 Spring Boot 的 Controller 就能看懂主体逻辑但里面关于会话模型和续期策略的取舍值得有经验的人多停一会儿。1.1 一个典型的多系统账号割裂现场先说清楚割裂是怎么长出来的。最开始只有一个系统登录逻辑写在UserController里密码用 MD5 存登录成功往 Session 里塞个userId完事。后来要做第二个系统工期紧直接把第一个系统的用户表复制过去登录接口复制过去改个包名就上线了。第三个系统是外采的人家有自己的用户体系只好做一次数据同步。第四个系统开始要做单点才发现前三个系统的密码加密方式都不一样同一个人的密码居然有三个版本。我们盘过一次一个不到 200 人的组织用户表有 4 张字段能对上的只有手机号和姓名其中两个系统的手机号还有 60% 是空的。这种状态下任何一次“某员工离职了把他所有权限关掉”的诉求都变成人工逐个系统点一遍的操作。更别说做登录审计——你连“谁在什么时候登录了哪个系统”都拿不到完整数据。统一登录要收编的正是这些账号唯一来源、密码策略统一、登录动作集中记录、登录态统一管理。这四件事只要有一件还留在业务系统里统一登录就是半成品。我见过不少项目只做了 Token 校验密码还是各系统自己存、自己改结果改密码的接口有四个用户改完一个另外三个还能用旧密码登等于白做。1.2 统一登录、单点登录、统一认证的边界划分这三个词经常被混着用实际落地时的边界得先划清楚否则需求会一直漂。概念关注点落地产物谁负责统一登录账号与密码只有一份登录动作集中发生认证中心 统一用户表认证中心单点登录登录一次多个系统共享登录态跨域凭证传递机制认证中心 各业务系统统一认证凭证的签发、校验、失效统一口径Token 规范 校验 SDK认证中心我的建议是先把统一登录做扎实再谈单点体验。顺序反过来的项目通常会在“要不要允许各系统保留本地账号做兜底”这个问题上纠缠三个月。我的答案是不留兜底认证中心挂了就整体挂这听起来激进但恰恰逼着你把认证中心的可用性做上去——认证中心背后只有一个 Redis 和一个用户库它的稳定性本来就该高于任何一个业务系统。1.3 先把目标写死登录态的三个硬指标在动手写第一行代码之前我们把验收标准定成了三条后面所有设计都是围绕这三条倒推的。第一条是凭证的无状态校验比例。业务系统每次请求都要判断“这个人登录了吗”如果每次都回调认证中心认证中心就会成为全站流量的单点一次抖动全站 502。所以我们的目标是80% 以上的校验走本地验签只有需要判断“是否已被踢下线”这类强一致场景才回源。第二条是登录态失效的传播延迟。用户点了登出或者在后台被管理员禁用其他系统多久能感知我们的底线是 30 秒靠短 TTL 加一轮主动通知达成。第三条是单点故障的爆炸半径。认证中心的 Redis 挂了会怎样我们的选择是降级为“已有 Token 仍可验签通过新登录全部失败”而不是全站瘫痪。这条直接决定了 Token 里必须带足够的自描述信息。这三条定完之后很多争论自动消失了。比如“Token 要不要存数据库”因为第一条和第三条的存在答案必然是存 Redis 且只存必要索引不做全量落库。2. 认证中心的形态选型Session、Token 还是标准协议选型这件事最怕的是“别人用什么我用什么”。我见过一个内部管理系统照抄了某开放平台的 JWT 方案结果因为没法主动踢人被安全部门打回来重做。形态选型必须回到你的实际场景客户端是什么、有多少个系统、要不要支持移动端、能不能接受短暂的不一致。2.1 三种会话模型的真实差异把常见的三种做法摊开对比差异其实很直观。维度服务端 Session纯 JWT 无状态Redis 索引 短期 JWT校验开销每次请求查存储本地验签零 IO本地验签为主敏感操作回源主动失效立即生效基本做不到秒级生效跨域支持需要 Cookie 域配置跨主域困难天然友好天然友好移动端/第三方不友好友好友好水平扩展需要会话共享无压力需要 Redis 高可用典型痛点多域名下 Cookie 传不过去无法踢人、无法改权限即时生效多一层 Redis 依赖服务端 Session 的问题不在技术在浏览器。一旦你的系统分布在多个二级域名下Cookie 的Domain属性能覆盖的范围有限跨主域就得走 URL 回传或者跳板页体验和安全性都打折。纯 JWT 的问题在于它把“信任”固化在了签发那一刻签出去之后你在服务端说什么它都不听除非引入黑名单——而一旦引入黑名单它就不再是无状态的了。所以真实项目里最后落在第三种短期 JWT 承担高频校验Redis 承担状态索引。这个组合不是妥协是把两种模型的优点各自用在它擅长的地方。JWT 的 15 分钟有效期意味着即使 Redis 完全不可用最坏情况下的风险窗口也就是 15 分钟而 Redis 里存的只是一个jti - userId的映射内存占用极小一个十万用户的系统索引数据撑死几十兆。2.2 为什么最终落在“Redis 索引 短期 JWT”有一个细节值得说清楚很多人以为用 JWT 就是为了不查库其实不完全对。真正的收益是把校验动作从“网络往返”变成“本地计算”。业务系统拿到 Token 之后验签、检查过期时间、解析出用户 ID全程不需要任何外部调用P99 耗时在微秒级。这对那些 QPS 上万的接口来说是刚需。但我们同时保留了一个 Redis 索引键是auth:jti:{jti}值是用户 ID 和签发时间TTL 略大于 Token 有效期。业务系统在两类场景下会回源查这个索引一是涉及资金、权限变更的敏感接口二是用户在客户端主动触发的“检查登录态”请求。这样做的好处是日常读接口不碰 Redis写接口和敏感接口保证强一致两头都照顾到了。提示索引的 TTL 一定要比 Token 有效期长一点比如 Token 15 分钟、索引 20 分钟。否则会出现 Token 还没过期、索引先没了的情况回源校验直接判失败用户看到的是“莫名其妙被登出”。2.3 协议层选型自研接口还是走标准协议这一层经常被过度设计。我的判断标准很简单如果你的系统只服务于自家前端和自家兄弟系统自研一套登录 刷新接口完全够用别急着上 OAuth2。标准协议的价值在于“第三方接入”和“用户授权”当你的场景里出现“让用户授权某个外部应用访问他的数据”时才真正需要它。我们当时的做法是折中对外提供一套标准的授权码流程给合作方对内则走自研的账号密码登录 Token 刷新。两套接口共用同一个用户表、同一套密码校验逻辑、同一个 Token 签发器只是入口不同。这样既没有把内部系统复杂化也留好了对外的口子。如果确实要走标准协议Spring 生态里有现成的授权服务器实现可以直接用但要注意版本兼容问题——Spring Boot 3.x 之后安全框架的主版本号变了配置写法和 2.x 差别不小网上大量教程还是老版本照抄会浪费不少时间。2.4 一个可落地的模块拆分把认证中心拆成三个模块是我试过最舒服的结构auth-core用户、密码、Token 签发与校验的核心逻辑不依赖 Web 层可以被任何入口复用。auth-api对外的 HTTP 接口登录、刷新、登出、校验只做参数校验和编排。auth-client给业务系统用的 SDK包含过滤器、用户上下文、Feign 透传打成 starter 引入。拆开的好处是auth-core可以被单元测试完整覆盖Token 签发这种逻辑一旦有 bug 就是安全事故必须能脱离容器跑测试。而auth-client单独发布版本业务系统升级 SDK 不影响认证中心认证中心改接口也不强制所有系统同时升级——只要保持向后兼容。3. 认证中心服务端的关键实现从登录接口到凭证签发服务端的代码量其实不大难点全在细节。密码怎么存、Token 怎么签、刷新怎么做、并发刷新怎么办这四件事每一件都有能让人返工的坑。3.1 密码存储与登录校验的正确姿势密码存储只有一个正确答案慢哈希 随机盐。BCrypt 或 Argon2 都行别用 MD5也别用 SHA-256 加固定盐。BCrypt 的强度参数cost设置在 10 到 12 之间是常见选择10 在普通服务器上单次校验约 50 到 100 毫秒这个延迟对登录接口来说完全可接受但对暴力破解来说成本极高。// 注册/改密时 String hash passwordEncoder.encode(rawPassword); // 登录校验时注意 isMatch 内部是常量时间比较 boolean ok passwordEncoder.matches(rawPassword, storedHash);有个容易忽略的点BCrypt 每次 encode 的结果都不一样盐是随机的所以不能用哈希值做等值查询只能先按用户名或手机号查出用户再比对哈希。这意味着登录接口必然有一次按用户名的索引查询这个字段一定要建唯一索引否则高并发下会退化成全表扫。配套的还有防爆破。我们的做法是三层同一账号连续失败 5 次锁定 5 分钟同一 IP 每分钟登录请求不超过 20 次登录接口本身加一层滑动窗口限流。锁定的计数存在 Redis 里键是auth:fail:{userId}用INCR加过期时间实现不用考虑分布式锁因为INCR本身是原子的。注意锁定策略要能通过后台手动解除否则测试环境天天锁人运维会来找你。我们后来加了一个管理接口把失败计数的键直接删掉。3.2 双 Token 设计与签发逻辑我们用的是 accessToken refreshToken 的组合。accessToken 是短期 JWT15 分钟refreshToken 是一个随机串7 天服务端存 Redis。这样设计的原因很直接JWT 一旦签发就无法撤销有效期必须短但让用户每 15 分钟重新输一次密码是不可能的所以需要一个长期的、可撤销的凭证来换新的 accessToken。public TokenPair issue(Long userId, String deviceId) { String jti UUID.randomUUID().toString().replace(-, ); Instant now Instant.now(); String access Jwts.builder() .setId(jti) .setSubject(String.valueOf(userId)) .claim(dev, deviceId) .setIssuedAt(Date.from(now)) .setExpiration(Date.from(now.plusSeconds(900))) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); String refresh RandomStringUtils.randomAlphanumeric(48); String rtKey auth:rt: refresh; redis.opsForValue().set(rtKey, userId : deviceId, Duration.ofDays(7)); // 反向索引用于踢人和单点登出时批量清理 redis.opsForSet().add(auth:user: userId, refresh); redis.expire(auth:user: userId, Duration.ofDays(7)); // 写入 jti 索引供敏感接口回源校验 redis.opsForValue().set(auth:jti: jti, String.valueOf(userId), Duration.ofMinutes(20)); return new TokenPair(access, refresh); }deviceId这个字段是我后来加的用来区分同一个用户的不同客户端。没有它的时候用户手机上登一次、电脑上登一次服务端只存一个 refreshToken结果就是后登的把先登的顶掉用户抱怨“我在电脑上好好的手机一登就被踢了”。加上设备标识之后每个设备一个 refreshToken踢人也可以精确到某一台设备。3.3 Redis 里的登录态结构设计键的设计值得单独说因为它决定了你后续能不能做批量操作。我们最终用了三组键键模式值TTL用途auth:rt:{refreshToken}userId:deviceId7 天refreshToken 换取新 accessTokenauth:user:{userId}Set of refreshToken7 天踢人、登出全部设备auth:jti:{jti}userId20 分钟敏感接口回源校验、黑名单第一组是核心映射第二组是反向索引。第二组的存在是为了避免用KEYS auth:rt:*去扫全库找某个用户的所有 refreshToken——那是灾难性操作。用 Set 存起来之后踢人就是一次SMEMBERS加若干个DELO(N) 但 N 是设备数通常不超过 5。还有一个隐藏需求管理员要能看到“这个用户现在在几个设备上登录”。有这个 Set 之后SCARD一行就出来了。上线之后这个能力被运营用得比我想的多用来判断异常登录很有帮助。3.4 续期、并发刷新与刷新风暴的处理刷新接口是全系统最容易被写出并发 bug 的地方。场景是这样的前端同时发起 5 个请求发现 accessToken 过期了5 个请求同时调刷新接口每个都拿同一个 refreshToken 去换服务端要是每次都给新 Token 并把旧 refreshToken 删掉那 4 个请求就失败了——这就是典型的刷新风暴。我们的解法是刷新操作幂等化换发新 accessToken 时refreshToken 不立即作废而是保留一个短暂的宽限期比如 60 秒。在宽限期内同一个 refreshToken 可以重复换取新 accessToken每次签发的新 accessToken 用同一个 jti 覆盖写SET覆盖而非新增。60 秒之后旧 refreshToken 才真正删除。public TokenPair refresh(String refreshToken) { String key auth:rt: refreshToken; String val redis.opsForValue().get(key); if (val null) throw new AuthException(凭证已失效请重新登录); String[] parts val.split(:); Long userId Long.valueOf(parts[0]); String deviceId parts[1]; // 滑动续期每次刷新把 TTL 重置回 7 天 redis.expire(key, Duration.ofDays(7)); // 签发新 accessToken旧 refreshToken 保留宽限期 return buildAccessOnly(userId, deviceId, refreshToken); }另一个思路是前端加锁用一个 Promise 保证同一时刻只有一个刷新请求在飞。这个方案在前端做是对的但不能只靠前端因为移动端、第三方调用方你控制不了。服务端幂等才是兜底。还有一个细节如果用户在 A 设备刷新了 refreshToken 的 TTLB 设备的 TTL 是不是也要跟着续我们的选择是不续各设备独立计算 7 天。理由是如果做成统一的“活跃即续期”一个用户只要有一台设备在用所有设备就永不过期安全性上说不过去。4. 业务系统的接入姿势过滤器、拦截器与网关认证中心做得再漂亮业务系统接入体验差推广就会被抵制。我们的目标是“引入一个依赖、加一行配置、搞定”实际上也确实做到了但过程中的取舍值得摊开讲。4.1 接入层的三种位置与各自的适用面业务系统在哪儿拦截请求做校验有三个位置可选Servlet 过滤器、Spring MVC 拦截器、API 网关。三者的差别不在于能不能做而在于能拿到什么上下文、能覆盖多大范围。过滤器的位置最靠前在 Spring MVC 的 HandlerMapping 之前能拿到原始的HttpServletRequest也能拦截静态资源请求。适合做“所有请求都必须带 Token”这种全局约束。缺点是拿不到 Controller 的方法和注解没法做细粒度的方法级放行。拦截器的位置在 HandlerMapping 之后能拿到HandlerMethod可以读注解。适合做Anonymous这种“这个方法允许匿名访问”的标记。缺点是它已经进入了 MVC 流程一些早期异常比如请求体解析失败它拦不到。网关则是另一层考虑如果你的系统已经统一走网关那在网关做一次校验、把用户 ID 通过请求头透传给下游下游系统连 SDK 都不用引。这个方案最省事但要求所有流量必须经过网关内网直连的调用绕不过去。我们的实际做法是网关做第一道SDK 做第二道。网关负责验签并把X-User-Id、X-Token-Jti写进请求头同时剥掉外部请求里自带的这两个头防止伪造。业务系统里的 SDK 负责二次校验并构建用户上下文。听起来冗余但内网调用、定时任务触发、消息消费这些场景不经过网关必须有 SDK 兜底。4.2 一个可复用的 starter 封装auth-client这个 starter 里核心就三样东西Component public class AuthFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException { String token resolveToken(req); // Header 优先其次 Cookie if (token ! null) { Claims claims tokenVerifier.verify(token); // 本地验签 if (claims ! null) { UserContext.set(new LoginUser( Long.valueOf(claims.getSubject()), claims.getId(), claims.get(dev, String.class))); } } try { chain.doFilter(req, resp); } finally { UserContext.clear(); // 必须清理见后文踩坑 } } }resolveToken的取值顺序要讲究先看Authorization: Bearer xxx其次看自定义头最后才看 Cookie。为什么 Cookie 放最后因为如果前端同时带了 Cookie 和 HeaderHeader 应该是用户当前操作的那个身份Cookie 可能是更早留下的。这个顺序搞反过导致用户在切换账号之后仍然被识别成旧账号。UserContext用ThreadLocal实现但要注意Web 容器是线程池线程会被复用。如果不在finally里清理下一个请求进来就会读到上一个请求的用户信息这是最典型也最危险的一类 bug后文会专门讲我踩的那次。4.3 跨服务调用时的用户上下文透传微服务拆分之后A 服务调 B 服务B 服务也得知道“当前是谁在操作”。做法是在 HTTP 客户端加一个拦截器把当前线程的用户信息塞进请求头。public class FeignAuthInterceptor implements RequestInterceptor { Override public void apply(RequestTemplate template) { LoginUser user UserContext.get(); if (user ! null) { template.header(X-User-Id, String.valueOf(user.getUserId())); template.header(X-Token-Jti, user.getJti()); template.header(X-Internal-Call, 1); } } }这里有个安全考量内部调用头X-Internal-Call必须在网关层被无条件剥掉否则外部请求伪造这个头就能绕过鉴权直接调到内网服务。我们在网关上做了一个请求头白名单只保留必要的那几个其余全部丢弃。如果用线程池做异步编排ThreadLocal会丢得换成支持父子线程传递的实现并且在提交任务时做一次快照拷贝。这个问题在引入异步之后一定会遇到建议一开始就封装好工具方法别让每个业务方自己new ThreadLocal。4.4 登出与踢人反向通知怎么做登出分两种用户主动点退出和管理员在后台踢人。主动退出好办删掉 refreshToken、删掉 jti 索引就行。难的是“已经签发出去的 accessToken 怎么立刻失效”。最直接的办法是每次校验都回源查 jti 索引查到不存在就拒绝。这能实现秒级失效但和前面“80% 走本地验签”的目标冲突。我们的折中是accessToken 有效期压到 15 分钟登出时只删 refreshTokenaccessToken 自然过期。用户点退出之后前端立即清掉本地存储所有后续请求都不会带 Token实际上已经登出了。真正需要“立刻”的场景是安全事件比如账号被盗这时候走一个特殊接口把用户的 jti 全部拉黑而敏感接口本来就回源校验能立刻拦住。如果想做得更彻底可以用一个广播机制通知所有在线节点刷新本地黑名单。我们试过一轮最终放弃了——因为多节点部署时消息到达的顺序无法保证反而容易出现“A 节点认为拉黑了、B 节点认为没有”的混乱状态。有这个精力不如把 accessToken 的有效期再压短一点。5. 上线之后才暴露的问题踩坑与排查链路前面讲的都是设计。真实项目里最耗时间的永远是“上线之后才发现”的那部分。下面这几个坑有的是我亲手挖的有的是帮别人排查的过程完整放出来方便你按图索骥。5.1 时钟漂移导致 Token 莫名过期现象是少数用户反馈“刚登录就提示登录过期”而且集中在某一批机器上。日志里看exp时间明明还没到。排查路径是这样的。第一步把出问题的 Token 解出来看iat和exp发现exp - iat确实是 900 秒令牌本身没问题。第二步对比签发节点的系统时间和校验节点的系统时间发现两者差了将近 4 分钟——签发节点跑在容器里容器宿主机的时钟没开同步长期运行之后漂移累积。第三步加日志打出now和exp的差值确认就是时钟问题。修复方案有两个层面。短期是加大校验侧的容忍窗口也就是验签时允许exp有 60 到 120 秒的偏移很多 JWT 库都支持这个配置。长期是把所有节点的时钟同步打开并且在签发时把iat往前推一点点宁可让 Token 早一点生效也别让它一签出来就在别人眼里过期了。注意容忍窗口不要设太大超过 5 分钟就失去了意义反而给了被撤销的 Token 更长的有效时间。5.2 跨域请求里 Cookie 传不过去的连环坑现象是单个系统访问正常从 A 系统跳转到 B 系统之后B 系统拿不到登录态用户被迫重新登录。打开浏览器开发者工具请求头里确实没有 Cookie。这个问题有好几层原因得逐层剥。第一层是SameSite属性浏览器默认策略下跨站请求不会带上 Cookie必须显式设为SameSiteNone; Secure。第二层是Domain属性要设置成共同的父域比如两个子域都设成Domain.example.internal注意前面的点。第三层是Path默认是当前路径得设成/。第四层才是 CORS 预检Access-Control-Allow-Credentials必须为true同时Access-Control-Allow-Origin不能是通配符必须是具体来源。我们当时卡在第二层和第三层的组合上Domain设对了但Path没设导致只在根路径下能读到 Cookie。定位方法是直接在浏览器里看 Cookie 的实际属性比看服务端配置快得多。对于完全不同的主域Cookie 方案基本上走不通这时候只能退回到 URL 参数传递一次性授权码由目标系统拿授权码去换 Token。这条路径要严格限制授权码的有效期我们的做法是 60 秒和一次性使用否则就是明显的安全漏洞。5.3 多端登录互斥产品需求和技术实现的错位上线两周之后产品提了个需求“同一个账号最多允许两台设备同时在线第三台登录要把最早的那台踢掉。”听起来简单做起来发现“设备”这个概念在 Web 端根本不存在——浏览器没有可靠的设备标识清缓存就变了。我们试过用浏览器指纹User-Agent 屏幕分辨率 时区结果发现同一台电脑换浏览器就被识别成新设备用户在公司用 Chrome、回家用 Edge两下就把名额占满了。最后改成“按 refreshToken 计数”每次登录生成一个新 refreshToken超过上限就按签发时间删掉最老的被踢的那台设备下次刷新时会失败提示“您的账号在其他设备登录”。这个方案不完美——用户在同一台电脑上开两个浏览器窗口在服务端看来就是两台设备。但产品接受了因为它的实际诉求是“防止账号被大量共享”按 Token 计数已经能覆盖 90% 的场景。技术方案和产品目标之间总有这种微调关键是别为了追求“真正的设备识别”去引入一堆指纹库那会把简单问题复杂化。5.4 一次登录态串号的完整排查过程这是我最不愿意回忆的一次事故。现象是极少数用户反馈“看到别人的数据了”。听到这个描述第一反应是数据权限的 SQL 写错了但查下来 SQL 没问题是用户上下文里拿到的userId不对。排查链路是这样的。第一步确认现象可复现在压测环境用两个账号交替高频请求大约几万次之后能复现一次。第二步怀疑缓存把用户上下文的缓存关掉仍然复现排除。第三步怀疑连接池或者请求转发在过滤器入口打出当前线程名和userId发现同一个线程名先后处理了两个不同用户的请求第二次请求读到的userId是第一次的。到这里原因就清楚了ThreadLocal没有清理。代码里finally块的UserContext.clear()是有的但有一类请求不走那个过滤器——内部的健康检查接口和静态资源它们被更靠前的一个 Filter 直接短路返回了chain.doFilter根本没执行finally也没执行。而这些短路路径在处理过程中线程被别的请求复用了。修复很简单把清理动作提到过滤器的入口处也就是进入时先clear()一次再set()这样即使上一次没清理干净这一次也会覆盖掉。同时把短路的那个 Filter 也加上清理。改完之后压测跑了 50 万次没再复现。这个坑给我的教训是任何依赖线程复用的上下文清理动作要放在“进入时”和“退出时”两个位置只靠finally是不够的因为你永远不知道有没有哪个分支把doFilter短路了。这个经验后来被我用在了所有类似场景里包括 MDC 日志上下文、事务上下文。关于限流和压测这里补一组我们线上的实际数据作参考认证中心的登录接口单实例 QPS 在 800 左右瓶颈在 BCrypt 的哈希计算CPU 打满Token 校验接口因为是纯本地验签单实例能跑到 2 万以上。这个比例说明一件事——把计算密集的密码校验和 IO 密集的校验接口混在同一个实例上是不划算的我们后来把登录接口单独拆了一组实例校验接口用更小的规格但更多的副本资源利用率明显好转。最后分享一个我一直在用的小习惯认证相关的所有接口日志里必须打jti不要只打userId。因为一个用户可能同时有多个 Token 在飞出问题的时候只看userId根本分不清是哪一次登录、哪一台设备。有了jti从网关日志到业务日志能一路串起来排查效率差一个数量级。这个字段的存储成本几乎为零但真出事的时候它会是你最快的那条线索。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑