双重认证模型
admin 服务在每一个请求上都回答两个不同的信任问题,并且把它们干净地分开。一个问题是谁在调用——内部服务之间传输层面的边界。另一个问题是这次调用背后是哪个人——决定能做什么的身份。两者各有一层,谁也不替谁干活。
第一层:服务间 JWT
内部流量用一个手写的 JSON Web Token 来认证,就是经典的三段式——header、payload、signature——用一个共享服务密钥、以 HMAC-SHA256 签名。没有外部库来做签名;代码库自己基于 Node 内置的 crypto 铸造和校验这个 token。
token 是按每次对外调用当场铸造的。签名一侧盖上签发时间,可选地盖上过期时间,并带上一个标明调用方服务的 claim。校验一侧是一个跑在路由处理之前的请求钩子:它要求带 bearer token,重新计算签名并比对,检查 header 声明的算法和类型是否符合预期,并拒绝已过期、或签发时间落在未来的 token——每个边界都留一点余量,容忍机器之间的时钟漂移。校验通过后,它把一小段上下文(调用方服务名加上原始 claims)挂到请求上;任何失败都抛 401。健康检查这类公开路由整个跳过这套检查。
效果是:每一个非公开的入站请求,在处理器看到它之前,都必须先证明自己来自一个受信任的内部服务。
第二层:管理会话守卫
服务 JWT 对人是谁只字不提——那是第二层的事。人的身份搭载在一个从浏览器转发过来的会话 cookie 上(另有一个开发环境下的回退)。admin 服务自己并不校验这个会话,而是由一个 helper 从请求里取出会话 cookie,交给 auth 服务自己的身份端点去解析,解析出来的用户——id、email、一个 user-type、以及 name——才是 admin 所信任的东西。
而这次解析调用本身又被包在第一层的服务 JWT 里,于是两层叠合起来:发往身份端点的那个请求,同时带着机器凭据和人的会话 cookie。
解析之上坐着两个守卫。一个只要求解析出任意用户,会话缺失或无法解析时以 401 失败。另一个专门要求是管理员用户,当解析出的 user-type 是别的东西时以 403 失败。是不是管理员,纯粹由 auth 服务返回的 user-type 推导,绝不在本地自行计算。
为什么是这个形状
这个设计守住了一个身份的单一真相来源:auth 服务。admin 服务对会话保持无状态——它不持有会话存储,也不从密码出发做任何授权判断。它只问"这个 cookie 是谁?"和"这个人是管理员吗?"。机器信任的问题和人身份的问题保持互相独立,正是这一点让浏览器请求和服务间请求可以走同一条代码路径,却按不同的证据来裁决。