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

comsince
FshareIM Team
很多统一认证中心不是一次性设计出来的,而是十年里一版一版迭代出来的。本文以一类典型的"自研统一认证中心"为原型(十年前基于早期 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
- 客户端发起换取令牌请求,凭证类型可以是账号密码、手机号、第三方账号、记住我密文,也可以是纯设备身份
- 认证提供方校验凭证——记住我场景还会额外做一次解密后的盐值二次校验
- 生成令牌本体:用户标识 + 随机令牌 ID + 指纹摘要 + 来源机房 + 版本号,编码后作为 Access Token 和 Refresh Token 下发(两者结构一致,不做签名)
- 完整鉴权信息(scope、权限、昵称、设备信息等)写入中心存储,令牌本体只是这份数据的查找键
- 用户资料(手机号、邮箱、头像)在同一次响应里一并塞进令牌返回体
- 登录事件异步投递到消息队列,供下游风控/统计消费
2. 鉴权 — 回源查询为默认路径
- 客户端携带令牌访问资源接口,服务端解析出令牌 ID 与指纹
- 命中降级开关或该客户端被标记强制本地校验 → 只做摘要比对,不查中心存储,返回一个简化身份
- 否则查询中心存储(带熔断保护)——这是默认路径
- 查不到、且令牌来自异地机房 → 再发起一次跨机房查询(同样带熔断保护),命中后回写本地
- 以上全部失败 → 拒绝访问
3. 续期 — 发新号,不追踪旧令牌
- 客户端携带 Refresh Token 请求续期
- 服务端校验令牌 ID 与中心存储记录一致,取出原有鉴权信息
- 基于原有信息重新生成一套全新的 Access Token + Refresh Token
- 旧的一套记录不做任何标记,留在中心存储里等待自然过期
4. 吊销 — 登出与令牌失效相互独立
- 网页点击退出 → 只销毁浏览器会话,不触碰任何令牌记录
- 要精确让某个令牌失效,业务方需要主动调用删除接口(自助或系统间)
- 删除动作 = 从中心存储里移除对应记录
- 若该令牌产自异地机房,再异步发起一次点对点通知,让对方机房执行同样的删除
- 账号状态变更(如注销)由消息队列驱动批量删除,通知异地机房的动作无重试、无补偿
三、新架构怎么做:完整流程
把新架构在同样四个环节里的实际动作逐条摊开,作为下一节对照的基础。
1. 签发 — 授权码 + PKCE 换 Token
- 客户端在跳转登录前,本地生成一对一次性校验值(明文挑战值与配对的验证值)
- 跳转到独立的授权服务器,用户完成登录、可能触发的 MFA、风控挑战、同意页授权
- 授权服务器回调客户端一个短时效的授权码
- 客户端用授权码 + 验证值去换令牌,授权服务器核对验证值与此前的挑战值是否匹配——不匹配则拒绝,从根本上堵住"授权码被他人截获后冒用"这条路
- 签发短寿命 JWT Access Token:用私钥签名,claim 只保留鉴权必需的最小字段(用户 ID、会话 ID、令牌 ID、签发方、受众、权限范围、令牌类型)
- 同时签发不透明 Refresh Token(随机串),落地到会话存储,记录所属会话、设备、客户端与状态(初始为"可用")
- 用户资料等展示性数据不随令牌下发,走独立端点按需获取
2. 鉴权 — 网关本地验签
- 客户端携带 JWT 访问网关
- 网关用本地持有、定期同步的公钥集合就地验签,不发起任何远程调用
- 验签通过后,继续核对签发方、受众、令牌类型、时间戳等声明是否满足当前系统的预期
- 查询本地内存黑名单缓存(由事件广播定期刷新),未命中即放行
- 对标记为高风险的接口,绕过本地缓存,强制查询中心黑名单存储,确保拿到的是最新状态
- 网关把验证后的最小身份信息通过请求头透传给下游服务,下游服务基于这些信息各自做资源级授权判断
3. 续期 — 轮换 + 复用检测
- 客户端携带 Refresh Token 请求续期
- 服务端加锁读取该令牌对应的会话记录,校验状态是否为"可用"、客户端与设备信息是否匹配
- 校验通过:把当前令牌标记为"已使用",签发一套全新的 Access Token + Refresh Token
- 若读到的记录状态已经是"已使用",说明这是同一个令牌被使用了第二次——判定为重放风险
- 触发会话级撤销:撤销这次登录签发过的全部令牌,并广播一次吊销事件
4. 吊销 — 会话级撤销 + 事件广播
- 无论用户从哪个端触发退出,都统一调用同一个撤销接口,参数是这次登录对应的会话标识
- 服务端把该会话标识(及名下所有令牌 ID)写入中心黑名单存储
- 立即发布一条吊销事件到消息总线
- 所有订阅该主题的网关节点、资源服务节点,各自更新自己的本地缓存
- 全局在数秒内完成状态收敛,此后无论走本地缓存还是回源查询,都会判定这些令牌已失效
四、流程全景对照
把两套流程按同一个环节并排放在一起,分野一眼就能看出来。
| 环节 | 旧架构关键动作 | 新架构关键动作 |
|---|---|---|
| 身份证明 | 凭密码/长效凭证直接核验,换取阶段无额外绑定校验 | 授权码 + 一次性验证值绑定,冒用授权码即失败 |
| 令牌形态 | 自描述查找键,不签名,真相在中心存储 | 签名 JWT,自带可验证的最小声明集 |
| 响应体设计 | 令牌与用户资料一并下发 | 令牌与用户资料解耦,资料走独立端点 |
| 鉴权默认路径 | 回源查询中心存储,本地校验仅故障态兜底 | 本地验签为默认路径,回源仅用于高风险场景强校验 |
| 鉴权容量特征 | 与中心存储的吞吐、网络状况强绑定 | 与认证中心解耦,随网关节点数水平扩展 |
| Refresh Token 状态 | 每次刷新发新号,旧令牌无标记、等自然过期 | 显式状态机(可用/已使用/已撤销),支持复用检测 |
| 会话与令牌关系 | 令牌各自独立存在,无统一会话标识贯穿 | 令牌隶属于会话标识,可按会话整体撤销 |
| 登出语义 | 仅清浏览器会话,令牌失效需业务方另行调用 | 登出直接等价于撤销该会话下全部令牌 |
| 多地域状态同步 | 点对点 HTTP 通知,按机房拓扑硬编码转发关系 | 发布/订阅事件广播,新增节点只需订阅 |
| 吊销一致性模型 | 强一致:删除即生效,依赖中心存储可用性 | 近实时一致:秒级收敛,不阻塞鉴权路径 |
这十项差异排开来看,没有一项是"新架构单方面更懂安全"——旧架构在吊销即时性、多端身份兼容、故障降级这几件事上,其实做得相当扎实,只是这些能力是靠不断加固中心化模型换来的。新架构也不是"凭空更安全",它的 PKCE、复用检测、会话级撤销,本质上是把"中心化模型十年里被迫补的洞",在设计之初就一次性规划进了流程本身。
五、这类旧架构常见的风险模式
以下是这类"十年历史、自研引用型令牌"的认证中心,在实践中容易暴露的几类共性风险,不针对具体某一套系统,而是这种设计模式在长期演进后普遍会遇到的问题。
1. 授权码可被冒领
如果签发环节没有一次性校验值的绑定,一旦授权码在跳转链路上被第三方截获(浏览器历史、代理日志、中间人),截获方可以直接拿这个授权码换取令牌,完成账号劫持,且用户本人的正常登录流程不会出现任何异常提示。这是纯粹的能力缺口,补齐 PKCE 的成本很低,性价比很高。
2. 峰值场景下鉴权与吊销时效性二选一
正常路径必须回源查询中心存储,一旦请求量在短时间内集中爆发(大促、抢券、热门直播等场景),中心存储或其网络链路容易成为全站瓶颈。触发降级后虽然保住了可用性,但降级期间往往无法感知令牌是否已被吊销——已经删号、改密、风控冻结的账号,在降级窗口内可能仍然被判定为合法。
3. Refresh Token 泄露后可持续续期
如果续期流程只是"发新号"而不追踪旧令牌状态,Refresh Token 一旦因客户端存储不当、抓包等方式泄露,攻击者可以持续用它换取新的 Access Token,服务端没有任何机制能识别出"这个令牌正在被两方同时使用"。这个风险会一直持续到令牌自然过期为止。
4. "退出登录"给用户错误的安全感
用户在公共设备或他人设备上完成操作后点击"退出登录",主观上认为自己已经安全下线,但只要没有其他环节主动调用删除接口,该设备/会话持有的令牌依然有效,直到令牌自然过期(可能长达数小时)。这是登出语义和令牌失效语义割裂的典型后果。
5. 跨机房失效通知的拓扑脆弱性
失效通知走点对点调用,转发条件和机房拓扑强绑定。机房数量变化、主备关系调整时,都需要重新梳理转发逻辑,边界条件判断一旦出错,容易出现通知互相触发的连锁问题,而这类问题往往要等实际发生后才会被发现——这也是为什么新架构倾向于用发布/订阅的事件广播取代点对点通知。
6. 单一密钥身兼数职
一些早期系统会用同一把非对称密钥同时用于跨机房通信加密、长效登录凭证加解密、单点登录断言加密。一旦这把密钥泄露,受影响的不是某一个功能,而是所有依赖它的机制同时失效,且没有版本化和轮换能力,无法做应急切换,只能停机换钥。密钥按用途拆分、引入版本号,是相对低成本就能补齐的一项治理动作。
六、优化路线图
面对一套已经稳定运行多年、承载核心账号体系的存量系统,比起"推翻重来换成 JWT",更现实的路径是按"改动小、见效快"到"需要地基先行"的顺序逐项补齐:
| 顺序 | 动作 | 对应风险 | 侵入性 |
|---|---|---|---|
| 1 | 签发环节加入一次性校验值绑定(PKCE) | 授权码可被冒领 | 低 |
| 2 | 令牌响应体与用户资料解耦 | 资料随令牌扩散暴露面 | 低 |
| 3 | 建立稳定的会话标识,贯穿一次登录产生的所有令牌 | 吊销/续期无法联动的地基 | 中 |
| 4 | Refresh Token 引入状态标记与复用检测 | 泄露后可持续续期 | 中 |
| 5 | "退出"统一触发会话级撤销 | 退出登录给错误安全感 | 中 |
| 6 | 跨机房失效通知由点对点改为事件广播 | 拓扑脆弱性 | 中 |
| 7 | 密钥按用途拆分并引入版本化 | 单一密钥身兼数职 | 中 |
| 8 | 高风险接口与普通接口分层设定降级策略 | 峰值场景下吊销时效性丢失 | 中高 |
前三项几乎不涉及令牌模型改造,是投入产出比最高的起点;第三项(会话标识)是后面所有会话类优化的地基,建议尽早规划。
七、结语
旧架构用"回源查询"换"吊销强一致";新架构用"吊销的可控延迟"换"鉴权可以水平扩展"。这不是一次技术优劣的评判,而是两份解题条件完全不同的答卷——旧架构在没有成熟 JWT 工具链、且对吊销即时性有强诉求的年代,做出的是当时最合理的权衡;新架构面对的是今天百万级 QPS、多地域部署成为常态的规模要求。
真正值得关注的,是新架构里那些与"是否用 JWT"正交的部分:身份证明环节的一次性校验绑定、响应体的解耦、Refresh Token 的状态机、会话与令牌的强关联、事件广播式的状态同步。这些是任何一套认证架构、在任何时代条件下都该具备的能力,而且大多可以在不推翻现有令牌模型的前提下逐步补齐——这也是这类存量系统最现实的演进节奏。