【后端】微服务后端鉴权方案
如何对 Helper Service 进行鉴权,核心原则是:Helper Service 不应该直接信任来自前端的请求,所有请求都必须经过 Core API 的授权和转发。
一、核心原则:信任链的建立
在架构设计中,我们必须明确“信任边界”。
- 前端:运行在用户浏览器中,是完全不可信的环境。任何来自前端的 Token、密钥都可能被窃取或篡改。
- Core API:是系统的“大脑”,是唯一验证用户身份(登录、颁发 Token)和权限的地方。它运行在受控的服务器环境中,是可信的。
- Helper Service:是系统的“手脚”,只负责执行特定任务。它本身不认识用户,只应该信任来自 Core API 的指令。它也是运行在受控的服务器环境中,是可信的。
因此,正确的信任链应该是:
用户 -> 前端 -> Core API -> Helper Service
前端拿着用户凭证(如 JWT)请求 Core API,Core API 验证通过后,以自己的身份去请求 Helper Service。Helper Service 只需要验证请求是不是来自 Core API 即可,无需关心最终用户是谁。
二、主流鉴权方案对比
基于上述原则,有几种主流的实现方案,各有优劣。
方案一:共享密钥 (Shared Secret / API Key)
这是最简单、最直接的方案,适用于大多数中小型项目或内部服务。
工作流程:
- 准备阶段:生成一个高强度、足够长的随机字符串作为共享密钥(例如
A1b2C3d4E5f6G7h8...
)。将这个密钥安全地配置在 Core API 和 Helper Service 的环境变量中。 - 请求阶段:
- 前端带着用户的 JWT 请求 Core API 的某个接口(例如
/api/v1/generate-report
)。 - Core API 验证 JWT 有效,确认用户有权限生成报告。
- Core API 向 Helper Service 发送请求(例如
POST /helper/reports
)。在请求头中加入共享密钥:POST /helper/reports Host: helper-service.example.com X-API-Key: A1b2C3d4E5f6G7h8... Content-Type: application/json { "userId": "123", "template": "monthly" }
- 前端带着用户的 JWT 请求 Core API 的某个接口(例如
- 鉴权阶段:
- Helper Service 收到请求后,从请求头
X-API-Key
中取出密钥。 - 将其与自己环境变量中存储的密钥进行比对。
- 如果一致,则请求合法,执行任务;否则,返回
401 Unauthorized
或403 Forbidden
。
优点:
- Helper Service 收到请求后,从请求头
- 实现简单:开发和理解成本极低。
- 性能高:只是一个简单的字符串比对,几乎没有计算开销。
- 无状态:Helper Service 无需维护任何会话状态。
缺点: - 密钥管理复杂:
- 轮换困难:如果密钥泄露,需要同时更新 Core API 和所有 Helper Service 的配置并重启,服务会有中断。
- 权限粒度粗:所有使用同一个密钥的 Core API 都拥有对 Helper Service 的全部权限。无法做到“Core API 的 A 模块只能调用 Helper Service 的 X 接口”。
- 安全性依赖:安全性完全依赖于密钥的保密性。一旦 Core API 服务器被入侵,密钥就可能泄露。
适用场景: - 服务数量少(1个 Core API + 少量 Helper Service)。
- 团队规模小,内部系统。
- 对权限控制要求不高的场景。
方案二:JWT 签名 (Internal JWT)
这个方案更优雅、更安全,是微服务间通信的推荐方案之一。
工作流程:
- 准备阶段:
- Core API 生成一对非对称密钥(如 RSA):一个私钥和一个公钥。
- Core API 保管好私钥(绝对不能泄露)。
- Helper Service 保管好公钥(可以公开)。
- 请求阶段:
- 前端带着用户的 JWT 请求 Core API。
- Core API 验证用户 JWT 有效后,自己生成一个新的、用于内部服务的 JWT(我们称之为 Internal JWT)。
- 这个 Internal JWT 的 Payload 可以包含一些必要的信息,比如:
{"iss": "core-api", // 签发者:Core API"sub": "helper-service-call", // 主题:表明这是一个内部调用"aud": "helper-service", // 接收者:Helper Service"exp": 1678886400, // 过期时间(可以设置得很短,比如5分钟)"permissions": ["report:generate"], // 精细的权限声明"user_context": { "userId": "123" } // 可选:传递一些用户上下文 }
- Core API 使用自己的私钥对这个 Internal JWT 进行签名。
- Core API 将这个签好名的 Internal JWT 放在请求头(如
Authorization: Bearer <internal-jwt>
)中,发送给 Helper Service。
- 鉴权阶段:
- Helper Service 收到请求后,取出 Internal JWT。
- 使用自己存储的公钥来验证 JWT 的签名。
- 验证 JWT 的其他声明:
iss
是否是core-api
?aud
是否是自己?exp
是否过期? - 所有验证通过后,解析出
permissions
,判断是否有权限调用当前接口,然后执行任务。
优点:
- 安全性高:基于非对称加密,Helper Service 即使被入侵,也无法伪造请求给其他服务(因为它没有私钥)。
- 无状态且可扩展:Helper Service 无需存储任何状态,只需通过公钥验证签名即可。可以轻松地增加新的 Helper Service,只需给它公钥即可。
- 信息丰富且精细:可以在 JWT Payload 中携带丰富的上下文信息和权限声明,实现非常精细的权限控制(例如,A 服务只能读,B 服务能读写)。
- 易于轮换:需要轮换密钥时,Core API 生成新的密钥对,然后逐步将 Helper Service 的公钥更新为新的即可,可以实现平滑过渡。
缺点: - 实现稍复杂:需要理解 JWT 和非对称加密的原理,代码实现上比共享密钥复杂一点。
- 有轻微性能开销:JWT 的签名和验证比字符串比对要消耗更多的 CPU 资源,但对于现代服务器来说,这点开销通常可以忽略不计。
适用场景: - 微服务架构,服务数量较多。
- 对安全性和权限控制有较高要求的系统。
- 需要未来扩展的系统。
方案三:服务网格 (Service Mesh, e.g., Istio, Linkerd)
这是一种更高级、更底层的方案,将鉴权等基础设施功能从业务代码中剥离出来。
工作流程:
- 部署阶段:在 Kubernetes 或其他容器编排平台中部署一个服务网格。每个服务(Core API, Helper Service)的旁边都会被注入一个轻量级的代理(称为 Sidecar,如 Envoy)。
- 配置阶段:通过服务网格的控制平面(如 Istio)配置策略,例如:
- “允许来自
core-api
服务的请求访问helper-service
服务的/reports
接口。” - “拒绝所有其他来源的请求。”
- “允许来自
- 请求阶段:
- Core API 的业务代码像往常一样,直接向
helper-service
的域名发送请求(不需要在代码里加任何鉴权逻辑!)。 - 这个请求实际上被 Core API 旁边的 Sidecar 拦截了。
- Sidecar 根据预设的策略,自动为请求加上身份证明(比如 mTLS 证书),然后转发给 Helper Service 的 Sidecar。
- Core API 的业务代码像往常一样,直接向
- 鉴权阶段:
- Helper Service 的 Sidecar 拦截到请求。
- 它验证请求来源的身份证书是否符合控制平面下发的策略。
- 如果验证通过,才将请求转发给 Helper Service 的业务代码。否则,直接拒绝。
优点:
- 业务代码无侵入:开发人员完全不需要关心服务间的鉴权逻辑,可以专注于业务实现。
- 统一管理和策略执行:所有服务的通信策略都在一个地方集中管理,非常清晰,不易出错。
- 功能强大:除了鉴权,服务网格还能提供流量管理、熔断、遥测、链路追踪等强大功能。
缺点: - 架构复杂:引入服务网格会极大地增加整个系统的复杂度,对运维和开发人员的要求很高。
- 资源开销:每个服务旁边都运行一个 Sidecar 代理,会带来额外的 CPU 和内存消耗。
- 学习曲线陡峭:需要投入大量时间学习和维护。
适用场景: - 大型、复杂的微服务集群。
- 拥有专业运维团队的公司。
- 对系统可观测性、安全性和流量控制有极致要求的场景。
三、最佳实践与建议
结合以上分析,我为你提供一个从易到难的演进建议:
阶段一:项目初期,快速验证 (推荐方案一)
- 选择:共享密钥。
- 做法:
- 使用
secretsmanager
或vault
等工具来管理共享密钥,而不是硬编码在代码或.env
文件里。 - 在 Core API 和 Helper Service 中,实现一个中间件,统一处理
X-API-Key
的验证。 - 确保所有 Helper Service 的接口都部署在内网,不对外暴露,增加一道物理屏障。
- 使用
- 理由:在项目初期,快速迭代是第一要务。共享密钥方案简单高效,能让你的团队快速把功能跑起来,同时基本的安全性也能得到保障。
阶段二:业务发展,服务增多 (推荐方案二)
- 选择:JWT 签名。
- 触发时机:当 Helper Service 的数量超过 3-5 个,或者你发现需要为不同的 Helper Service 配置不同的调用权限时。
- 做法:
- 设计一个标准的 Internal JWT 结构,包含
iss
,aud
,exp
,permissions
等关键字段。 - 在 Core API 中封装一个
callHelperService
的工具函数或库,内部自动完成 Internal JWT 的生成和请求发送。 - 在 Helper Service 中,使用成熟的 JWT 库(如
jsonwebtoken
for Node.js,PyJWT
for Python)来验证签名和解析权限。 - 同样,私钥和公钥的管理要使用专业的密钥管理工具。
- 设计一个标准的 Internal JWT 结构,包含
- 理由:这个方案在安全性和可维护性上取得了很好的平衡。它为系统的长期发展奠定了坚实的基础,是微服务架构的“甜点区”方案。
阶段三:规模庞大,体系成熟 (考虑方案三)
- 选择:服务网格。
- 触发时机:当你的微服务数量达到几十甚至上百个,团队规模扩大,手动管理服务间通信和策略变得非常痛苦时。
- 做法:
- 评估并选择一个服务网格产品(Istio 是最流行的选择)。
- 组建或培养一支具备 SRE(网站可靠性工程)能力的运维团队。
- 逐步将服务迁移到服务网格中,先从非核心业务开始,逐步推广。
- 理由:此时,架构的复杂性已经超出了人工管理的范畴。服务网格通过将基础设施能力下沉,能够统一、自动化地解决这些问题,提升整个系统的稳定性和效率。
总结
方案 | 实现复杂度 | 安全性 | 权限粒度 | 可扩展性 | 推荐场景 |
---|---|---|---|---|---|
共享密钥 | 低 | 中 | 粗 | 中 | 项目初期、少量内部服务 |
JWT 签名 | 中 | 高 | 细 | 高 | 微服务架构、主流选择 |
服务网格 | 高 | 极高 | 极细 | 极高 | 大规模复杂系统 |
对于绝大多数项目来说,从共享密钥开始,并在适当时机迁移到 JWT 签名方案,是最务实和明智的选择。它能很好地平衡开发效率、系统安全和未来的可扩展性。 |