十年自研认证中心复盘:从令牌查找键到 JWT 本地验签,两代 OAuth 2.0 架构的选择背景与流程全景

comsince

comsince

FshareIM Team

十年自研认证中心复盘:两代 OAuth 2.0 架构的选择与流程全景

很多统一认证中心不是一次性设计出来的,而是十年里一版一版迭代出来的。本文以一类典型的"自研统一认证中心"为原型(十年前基于早期 Spring Security OAuth2 深度定制、经过多轮迭代、支撑千万级用户的真实生产系统的常见形态),完整还原它在签发、鉴权、续期、吊销四个环节的真实执行路径,对照今天从零设计一套 OAuth 2.0 认证中心会怎么做,并给出每一处差异背后的选择背景与风险模式。


一、选择背景:两个时代的约束条件

同样是"统一认证中心",一类系统诞生于近十年前,另一类是今天从零设计的默认答案。把各自的约束条件摆出来,才能看清楚为什么会走向两条不同的路——这不是一次"谁更先进"的评判,而是两份解题条件完全不同的答卷。

十年前:存量架构落地时的解题条件

  • OAuth 2.0 规范(RFC 6749)刚发布不久,JWT(RFC 7519)、PKCE(RFC 7636)等配套标准要么还未成型,要么远未普及,业界缺少成熟的签名令牌工具链
  • 安全与业务团队有明确诉求:账号被盗、密码泄露后,令牌必须"删除即生效",不能接受任何吊销延迟窗口
  • 机房数量少、拓扑简单,"点对点通知"在当时的规模下是够用、且成本最低的方案
  • 移动互联网早期,Web、App、路由器等 IoT 设备的身份形态开始分化,需要一套体系同时兼容用户身份与设备身份

在这些条件下,"令牌只是查找键、真相只有一份放在中心存储"是最自然的选择——用最简单的模型换来最强的一致性,代价是要不断堆叠熔断、降级、多级查询去对冲中心化带来的可用性风险。这条对冲的路,很多这类系统走了十年,也确实沉淀出一套成熟的多级降级体系。

今天:从零设计时的解题条件

  • JWT、PKCE、JWT 安全最佳实践(RFC 8725)等标准已经沉淀多年,签名验签、密钥管理的工具链成熟且开箱即用
  • 业务体量的量级变了:中台型业务网关可能要扛百万级 QPS,若每次鉴权都回源,中心存储的容量天花板会直接锁死整个平台的扩展上限
  • 多可用区、多地域部署成为常态,事件总线类基础设施已经是标配,"点对点通知"在 N 个地域下的维护成本已不可控
  • 安全诉求依然存在,但工程共识已经转变为"可以接受一个被严格控制、足够短的吊销传播窗口",用它换取水平扩展能力

在这些条件下,"先接受近实时一致,再用本地缓存 TTL 和事件广播把窗口尽量收窄"成为新的自然选择——用架构上的确定性换取容量上的可预测性,鉴权路径不再随认证中心的可用性一起摇摆。

两套条件叠在一起看会发现:新架构不是"更懂安全",旧架构也不是"不懂扩展"——只是各自面对的规模、工具链成熟度、部署拓扑完全不同,选择自然也不同。


二、旧架构怎么做:完整流程

在看新架构之前,先把这类自研认证中心在四个环节里的实际动作逐条摊开——它是上一节"选择背景"落地成的具体产物。

1. 签发 — 登录换 Token

  1. 客户端发起换取令牌请求,凭证类型可以是账号密码、手机号、第三方账号、记住我密文,也可以是纯设备身份
  2. 认证提供方校验凭证——记住我场景还会额外做一次解密后的盐值二次校验
  3. 生成令牌本体:用户标识 + 随机令牌 ID + 指纹摘要 + 来源机房 + 版本号,编码后作为 Access Token 和 Refresh Token 下发(两者结构一致,不做签名
  4. 完整鉴权信息(scope、权限、昵称、设备信息等)写入中心存储,令牌本体只是这份数据的查找键
  5. 用户资料(手机号、邮箱、头像)在同一次响应里一并塞进令牌返回体
  6. 登录事件异步投递到消息队列,供下游风控/统计消费

2. 鉴权 — 回源查询为默认路径

  1. 客户端携带令牌访问资源接口,服务端解析出令牌 ID 与指纹
  2. 命中降级开关或该客户端被标记强制本地校验 → 只做摘要比对,不查中心存储,返回一个简化身份
  3. 否则查询中心存储(带熔断保护)——这是默认路径
  4. 查不到、且令牌来自异地机房 → 再发起一次跨机房查询(同样带熔断保护),命中后回写本地
  5. 以上全部失败 → 拒绝访问

3. 续期 — 发新号,不追踪旧令牌

  1. 客户端携带 Refresh Token 请求续期
  2. 服务端校验令牌 ID 与中心存储记录一致,取出原有鉴权信息
  3. 基于原有信息重新生成一套全新的 Access Token + Refresh Token
  4. 旧的一套记录不做任何标记,留在中心存储里等待自然过期

4. 吊销 — 登出与令牌失效相互独立

  1. 网页点击退出 → 只销毁浏览器会话,不触碰任何令牌记录
  2. 要精确让某个令牌失效,业务方需要主动调用删除接口(自助或系统间)
  3. 删除动作 = 从中心存储里移除对应记录
  4. 若该令牌产自异地机房,再异步发起一次点对点通知,让对方机房执行同样的删除
  5. 账号状态变更(如注销)由消息队列驱动批量删除,通知异地机房的动作无重试、无补偿

三、新架构怎么做:完整流程

把新架构在同样四个环节里的实际动作逐条摊开,作为下一节对照的基础。

1. 签发 — 授权码 + PKCE 换 Token

  1. 客户端在跳转登录前,本地生成一对一次性校验值(明文挑战值与配对的验证值)
  2. 跳转到独立的授权服务器,用户完成登录、可能触发的 MFA、风控挑战、同意页授权
  3. 授权服务器回调客户端一个短时效的授权码
  4. 客户端用授权码 + 验证值去换令牌,授权服务器核对验证值与此前的挑战值是否匹配——不匹配则拒绝,从根本上堵住"授权码被他人截获后冒用"这条路
  5. 签发短寿命 JWT Access Token:用私钥签名,claim 只保留鉴权必需的最小字段(用户 ID、会话 ID、令牌 ID、签发方、受众、权限范围、令牌类型)
  6. 同时签发不透明 Refresh Token(随机串),落地到会话存储,记录所属会话、设备、客户端与状态(初始为"可用")
  7. 用户资料等展示性数据不随令牌下发,走独立端点按需获取

2. 鉴权 — 网关本地验签

  1. 客户端携带 JWT 访问网关
  2. 网关用本地持有、定期同步的公钥集合就地验签,不发起任何远程调用
  3. 验签通过后,继续核对签发方、受众、令牌类型、时间戳等声明是否满足当前系统的预期
  4. 查询本地内存黑名单缓存(由事件广播定期刷新),未命中即放行
  5. 对标记为高风险的接口,绕过本地缓存,强制查询中心黑名单存储,确保拿到的是最新状态
  6. 网关把验证后的最小身份信息通过请求头透传给下游服务,下游服务基于这些信息各自做资源级授权判断

3. 续期 — 轮换 + 复用检测

  1. 客户端携带 Refresh Token 请求续期
  2. 服务端加锁读取该令牌对应的会话记录,校验状态是否为"可用"、客户端与设备信息是否匹配
  3. 校验通过:把当前令牌标记为"已使用",签发一套全新的 Access Token + Refresh Token
  4. 若读到的记录状态已经是"已使用",说明这是同一个令牌被使用了第二次——判定为重放风险
  5. 触发会话级撤销:撤销这次登录签发过的全部令牌,并广播一次吊销事件

4. 吊销 — 会话级撤销 + 事件广播

  1. 无论用户从哪个端触发退出,都统一调用同一个撤销接口,参数是这次登录对应的会话标识
  2. 服务端把该会话标识(及名下所有令牌 ID)写入中心黑名单存储
  3. 立即发布一条吊销事件到消息总线
  4. 所有订阅该主题的网关节点、资源服务节点,各自更新自己的本地缓存
  5. 全局在数秒内完成状态收敛,此后无论走本地缓存还是回源查询,都会判定这些令牌已失效

四、流程全景对照

把两套流程按同一个环节并排放在一起,分野一眼就能看出来。

环节旧架构关键动作新架构关键动作
身份证明凭密码/长效凭证直接核验,换取阶段无额外绑定校验授权码 + 一次性验证值绑定,冒用授权码即失败
令牌形态自描述查找键,不签名,真相在中心存储签名 JWT,自带可验证的最小声明集
响应体设计令牌与用户资料一并下发令牌与用户资料解耦,资料走独立端点
鉴权默认路径回源查询中心存储,本地校验仅故障态兜底本地验签为默认路径,回源仅用于高风险场景强校验
鉴权容量特征与中心存储的吞吐、网络状况强绑定与认证中心解耦,随网关节点数水平扩展
Refresh Token 状态每次刷新发新号,旧令牌无标记、等自然过期显式状态机(可用/已使用/已撤销),支持复用检测
会话与令牌关系令牌各自独立存在,无统一会话标识贯穿令牌隶属于会话标识,可按会话整体撤销
登出语义仅清浏览器会话,令牌失效需业务方另行调用登出直接等价于撤销该会话下全部令牌
多地域状态同步点对点 HTTP 通知,按机房拓扑硬编码转发关系发布/订阅事件广播,新增节点只需订阅
吊销一致性模型强一致:删除即生效,依赖中心存储可用性近实时一致:秒级收敛,不阻塞鉴权路径

这十项差异排开来看,没有一项是"新架构单方面更懂安全"——旧架构在吊销即时性、多端身份兼容、故障降级这几件事上,其实做得相当扎实,只是这些能力是靠不断加固中心化模型换来的。新架构也不是"凭空更安全",它的 PKCE、复用检测、会话级撤销,本质上是把"中心化模型十年里被迫补的洞",在设计之初就一次性规划进了流程本身。


五、这类旧架构常见的风险模式

以下是这类"十年历史、自研引用型令牌"的认证中心,在实践中容易暴露的几类共性风险,不针对具体某一套系统,而是这种设计模式在长期演进后普遍会遇到的问题。

1. 授权码可被冒领

如果签发环节没有一次性校验值的绑定,一旦授权码在跳转链路上被第三方截获(浏览器历史、代理日志、中间人),截获方可以直接拿这个授权码换取令牌,完成账号劫持,且用户本人的正常登录流程不会出现任何异常提示。这是纯粹的能力缺口,补齐 PKCE 的成本很低,性价比很高。

2. 峰值场景下鉴权与吊销时效性二选一

正常路径必须回源查询中心存储,一旦请求量在短时间内集中爆发(大促、抢券、热门直播等场景),中心存储或其网络链路容易成为全站瓶颈。触发降级后虽然保住了可用性,但降级期间往往无法感知令牌是否已被吊销——已经删号、改密、风控冻结的账号,在降级窗口内可能仍然被判定为合法。

3. Refresh Token 泄露后可持续续期

如果续期流程只是"发新号"而不追踪旧令牌状态,Refresh Token 一旦因客户端存储不当、抓包等方式泄露,攻击者可以持续用它换取新的 Access Token,服务端没有任何机制能识别出"这个令牌正在被两方同时使用"。这个风险会一直持续到令牌自然过期为止。

4. "退出登录"给用户错误的安全感

用户在公共设备或他人设备上完成操作后点击"退出登录",主观上认为自己已经安全下线,但只要没有其他环节主动调用删除接口,该设备/会话持有的令牌依然有效,直到令牌自然过期(可能长达数小时)。这是登出语义和令牌失效语义割裂的典型后果。

5. 跨机房失效通知的拓扑脆弱性

失效通知走点对点调用,转发条件和机房拓扑强绑定。机房数量变化、主备关系调整时,都需要重新梳理转发逻辑,边界条件判断一旦出错,容易出现通知互相触发的连锁问题,而这类问题往往要等实际发生后才会被发现——这也是为什么新架构倾向于用发布/订阅的事件广播取代点对点通知。

6. 单一密钥身兼数职

一些早期系统会用同一把非对称密钥同时用于跨机房通信加密、长效登录凭证加解密、单点登录断言加密。一旦这把密钥泄露,受影响的不是某一个功能,而是所有依赖它的机制同时失效,且没有版本化和轮换能力,无法做应急切换,只能停机换钥。密钥按用途拆分、引入版本号,是相对低成本就能补齐的一项治理动作。


六、优化路线图

面对一套已经稳定运行多年、承载核心账号体系的存量系统,比起"推翻重来换成 JWT",更现实的路径是按"改动小、见效快"到"需要地基先行"的顺序逐项补齐:

顺序动作对应风险侵入性
1签发环节加入一次性校验值绑定(PKCE)授权码可被冒领
2令牌响应体与用户资料解耦资料随令牌扩散暴露面
3建立稳定的会话标识,贯穿一次登录产生的所有令牌吊销/续期无法联动的地基
4Refresh Token 引入状态标记与复用检测泄露后可持续续期
5"退出"统一触发会话级撤销退出登录给错误安全感
6跨机房失效通知由点对点改为事件广播拓扑脆弱性
7密钥按用途拆分并引入版本化单一密钥身兼数职
8高风险接口与普通接口分层设定降级策略峰值场景下吊销时效性丢失中高

前三项几乎不涉及令牌模型改造,是投入产出比最高的起点;第三项(会话标识)是后面所有会话类优化的地基,建议尽早规划。


七、结语

旧架构用"回源查询"换"吊销强一致";新架构用"吊销的可控延迟"换"鉴权可以水平扩展"。这不是一次技术优劣的评判,而是两份解题条件完全不同的答卷——旧架构在没有成熟 JWT 工具链、且对吊销即时性有强诉求的年代,做出的是当时最合理的权衡;新架构面对的是今天百万级 QPS、多地域部署成为常态的规模要求。

真正值得关注的,是新架构里那些与"是否用 JWT"正交的部分:身份证明环节的一次性校验绑定、响应体的解耦、Refresh Token 的状态机、会话与令牌的强关联、事件广播式的状态同步。这些是任何一套认证架构、在任何时代条件下都该具备的能力,而且大多可以在不推翻现有令牌模型的前提下逐步补齐——这也是这类存量系统最现实的演进节奏。