服务间 JWT 信任边界
认证服务把每一个请求都当成不可信的,直到它证明自己来自一个已知的调用方。一个 Fastify 插件在整个应用前面挂上一个 onRequest 钩子,于是信任校验发生在任何路由处理器之前——只有一个咽喉点,而不是每条路由各自判断。
两条进入的路。 请求可以在 Authorization 头里带一个服务令牌(Bearer 方案),或者——对那些从不持有共享密钥的调用方而言——带一个由签名密钥握手建立起来的会话。两者都不满足的,一律回一个干脆的 401。健康检查端点是有意的例外:它们在一张忽略清单上,无需令牌即可通过,而且这张清单可以追加额外的路由,从而有意地放开特定路由。
身份由验签成功的那把密钥决定,而不是令牌里的声明。 插件持有一份小小的可信调用方注册表,每个调用方都绑定它自己的签名密钥。它拿令牌逐一去试每把密钥;调用方的身份由哪一把密钥验签成功来决定,而令牌自己声称的"我是谁"字段在这个判断里从不被信任。这是承重的设计选择——它让调用方的身份是密码学绑定的,而不是自我声称的,于是令牌无法谎报自己的来源。有些调用方还被额外标记为足够可信,登录时可以跳过人机校验那一步,因为它专属的密钥本身就是可信的证明。
令牌是一个手写的、基于 HMAC-SHA256 的 JWT。 签名与验证直接建在 Node 的 crypto 原语上,而不是用某个 JWT 库。验证会重新计算 header 与 payload 上的签名,任何不匹配都拒绝,并且还要求 header 声明的算法与类型符合预期——所以令牌无法降级或替换自己的算法。过期(exp)与签发时间(iat)会对着当前时间校验,并留了一个很小的、有界的时钟偏移容差,于是机器之间几秒的漂移不会造成误拒,而真正过期或来自未来的令牌仍会被拒绝。
要吵,不要哑。 可信调用方注册表在启动时从配置里装配,每一把必需的密钥都是强制的——只要缺一把,服务就拒绝启动,而不是悄悄地跑着却无法认证那个调用方。配置出错是一次崩溃,而不是一个静默的洞。
这个边界不是什么。 它只回答"这个请求来自一个可信的服务吗?"它不提取用户身份——那是另一件事,由会话 cookie 承载、在别处处理。把两者分开,服务信任校验才能保持简单、只干一件事。
相关
- youteacher_auth ——这个边界所守护的那个认证服务