客户身份与访问管理(CIAM)系统负责认证客户、管理身份生命周期与同意交互,并签发 Token。API 网关可以验证访问 Token 并执行粗粒度路由策略;应用仍需判断该主体在当前状态下是否能操作目标账户、订单、文档或工作流。
成功集成的关键是保留这条证据链。不要把任意 sub、邮箱、组或转发请求头合并成通用“可信用户”字段,也不能仅因为 OpenID Connect ID Token 同样采用 JWT,就把它当作访问 Token 接受。
关键要点
- 面向用户的客户端应采用符合当前安全实践的授权码流程,绝不能在 API 客户端收集客户密码。
- 针对具体 API 验证 Token 类型、签发者、签名、受众、时间、客户端和所需权限。
- 在本地 JWT 验证和 Token 内省之间明确选择,并定义新鲜度、撤销、缓存、超时与故障行为。
- 把 Scope、角色、组、权益和同意记录视为不同策略输入。
- 只在经过认证且无法绕过的链路上传递上游所需的身份上下文。
- 对资源和工作流的授权仍由应用负责。
把每项决策交给正确组件
1sequenceDiagram
2 participant U as 客户
3 participant C as 客户端应用
4 participant I as CIAM 授权服务器
5 participant G as API 网关
6 participant A as 应用与资源
7 U->>C: 开始登录或同意流程
8 C->>I: 携带资源、Scope 与 PKCE S256 code_challenge 的授权请求
9 I->>U: 认证并获取所需同意
10 I-->>C: 授权码
11 C->>I: 使用 PKCE code_verifier 及适用的客户端认证兑换授权码
12 I-->>C: 访问 Token 和可选 ID Token
13 C->>G: 携带访问 Token 的 API 请求
14 G->>G: 验证 Token 与粗粒度路由策略
15 G->>A: 请求与最小化已验证上下文
16 A->>A: 检查账户对象工作流与当前同意
17 A-->>G: 业务响应或拒绝
18 G-->>C: API 响应该图展示逻辑职责流。公有客户端使用 PKCE,不能依赖共享客户端密钥;机密客户端在要求时进行客户端认证,也应使用 PKCE。RFC 9700 要求公有客户端使用 PKCE,建议机密客户端也使用,并指出 S256 是目前不会在授权请求中暴露 code_verifier 的挑战方法。授权服务器必须验证 code_verifier 并防止 PKCE 降级。JWT 验证可以使用本地缓存密钥;不透明 Token 验证可能调用内省端点;同意可能在授权前或授权过程中获取。应记录实际采用的路径,而不是假设每个 API 请求都同步调用 CIAM。
区分 ID Token 与访问 Token
OpenID Connect 在 OAuth 2.0 之上提供认证。ID Token向客户端传递认证信息,并以该客户端为受众;访问 Token则提交给 API 等资源服务器。
使用相同 JWT 语法并不意味着两种 Token 可以互换。Token 的类型、签发者、受众或预期用途与受保护 API 不匹配时,网关应拒绝它。把 ID Token 转发给每个后端,还会泄露这些服务并不需要的客户声明。
RFC 9700规定不得使用资源所有者密码凭据授权。面向客户的应用不应要求用户在应用或低代码工作流中输入 CIAM 密码。应使用适当的重定向授权流程,并根据客户端类型保护授权码和刷新 Token。
为每个 API 定义 Token 合同
对于遵循 RFC 9068 的 JWT 访问 Token 部署,资源服务器需要验证必需声明和密码学属性,包括 Token 类型、精确签发者、签名与允许算法、目标受众和过期时间。该规范要求包含 iss、exp、aud、sub、client_id 和 iat 等声明;未声明遵循该规范的部署可能采用不同合同。
一组说明性的解码声明如下:
1{
2 "iss": "https://login.example.com/",
3 "sub": "cust_2f8a1",
4 "aud": "https://api.example.com/orders",
5 "exp": 1792464000,
6 "iat": 1792460400,
7 "jti": "at_01K7YB1F",
8 "client_id": "mobile-orders",
9 "scope": "orders.read orders.cancel",
10 "tenant_id": "org_73a2"
11}这是解码后的内容,不是验证成功的证明。必须检查签名、签发者元数据、密钥选择、算法策略、当前时间、受众和所需权限。tenant_id 是应用特定声明,必须定义其语义和签发者归属;它存在并不能证明主体可以访问该租户中的所有对象。
如果隐私模型要求降低跨服务关联,应使用成对或资源特定的主体标识符。RFC 9068 还提醒:资源服务器只应接收它有权获得的声明,包括基于同意或组织策略的声明。
有意识地选择 JWT 验证或内省
| 维度 | 本地 JWT 验证 | Token 内省 |
|---|---|---|
| 请求时依赖 | 可以缓存签发者密钥和元数据 | 通常依赖授权服务器响应或缓存 |
| 撤销新鲜度 | 通常受 Token 生命周期与其他撤销设计约束 | 可反映当前服务器状态,但仍受缓存影响 |
| Token 信息暴露 | 声明对持有者和资源服务器可见 | 不透明 Token 可把声明留在服务端 |
| 故障行为 | 策略允许时,可继续用已有缓存密钥验证 | CIAM 不可用时调用可能失败或超时 |
| 隐私与大小 | 声明随每个 Token 传输 | 可针对受保护资源最小化响应 |
RFC 7662定义 Token 内省及其必需的 active 布尔值。active: true 表示授权服务器按自身规则认为 Token 处于活跃状态;网关仍须执行目标受众与权限检查,应用也仍负责资源授权。对于 active: false 的已授权响应,通常不会说明 Token 为何无效。超时或响应格式错误不等于 active: false;应定义失败关闭响应、有界缓存、指标和事件路径,而不是虚构活跃状态。
如果缓存内省响应,撤销和权益变更可能要等缓存项过期后才生效;如果本地验证 JWT,缩短 Token 生命周期可以减少但不能消除新鲜度取舍。应按风险与可用性选择合同,而不是追逐 Token 格式潮流。
在网关执行受众与权限检查
RFC 9700 建议对访问 Token 限制受众和权限。为个人资料 API 签发的 Token 不能仅因签发者和签名有效,就被重放到订单 API。
每条路由都应定义:
- 可接受的签发者或信任域;
- 访问 Token 类型和允许算法;
- 精确的 API 受众或资源指示符;
- 必需 Scope 或授权细节;
- Token 代表用户、客户端,还是两者;
- 适用时的发送方约束证明;
- Token 和元数据缓存的最大年龄;
- 时钟偏差策略;
- 拒绝状态和安全错误细节。
Apache APISIX 3.19.0 的 openid-connect 插件可以强制要求 Bearer Token 和 Scope,并验证所配置的签发者与受众。零信任网关指南提供了固定版本的配置,并说明其中改变行为的布尔参数默认值和两条路径。应复用一份已审阅身份策略,而不是在各路由复制细微不同的配置。
分开管理同意、Scope 与授权
- 同意记录客户针对特定用途、客户端、资源、数据类别和时间所同意的事项。
- Scope 是为 Token 受众解释的权限字符串。
- 角色或组 是一种分配,其权威性与生命周期取决于签发方。
- 权益表示由产品、合同、账户或策略授予的访问权限。
- 应用授权判断当前主体此刻能否对该资源执行该操作。
orders.read 等 Scope 可以允许某类路由,但应用仍要检查订单是否属于正确客户或受委托组织。同意撤回可能需要更新 CIAM、撤销或等待 Token 过期,并在应用侧执行。长期 Token 缓存不能静默延长同意决策,使其超过已定义的新鲜度。
不要把每项持续变化的业务权限都塞进大型 Token。随着 Token 积累账户状态,陈旧声明、请求头大小、隐私暴露和跨服务耦合都会增加。
传播最小化的已验证上下文
验证后,网关可以传递标准化主体、客户端、租户、Scope 集合、认证上下文或策略决定。必须保护这条链路:
- 移除调用方提供的内部身份请求头副本;
- 只添加从已验证 Token 或批准策略服务派生的值;
- 认证并加密网关到上游的流量;
- 阻止绕过清理与验证路径的后端直连;
- 让应用区分用户主体和客户端身份;
- 除非上游必须验证或使用,否则不要转发原始 Token 或完整声明集。
内部请求头只有在这条完整边界内才可信。一个请求头名称本身并不是身份协议。
测试生命周期与失败路径
不要只测试正常路径:
| 测试 | 预期证据 |
|---|---|
| 错误签发者或受众 | 网关在调用上游前拒绝 |
| 过期、尚未生效、格式错误或类型错误的 Token | 返回安全 Token 错误,不泄露声明 |
| 缺少所需 Scope | 路由拒绝,不调用应用 |
| 路由 Scope 有效但账户对象错误 | 应用拒绝 |
| 已撤销不透明 Token | 内省返回 inactive,缓存行为符合合同 |
| CIAM 超时 | 执行已定义失败或有界缓存决策,不虚构 active 状态 |
| 签名密钥轮换 | 受控刷新后接受新密钥,未知密钥安全失败 |
| 同意撤回 | 在已记录的 Token/缓存/应用窗口内生效 |
| 凭证或客户端禁用 | 阻止新授权,并验证已有 Token 策略 |
| 尝试直连后端 | 网络或应用拒绝绕过 |
在隐私控制下记录签发者、受众决定、客户端、主体引用、策略原因、Token 标识符哈希或安全引用、配置版本和关联 ID。不要记录访问 Token 或不必要的客户声明。
CIAM 集成检查清单
- 是否按用途与验证合同区分 ID Token 和访问 Token?
- 是否检查签发者、签名、算法、受众、时间、客户端和权限?
- 是否禁止资源所有者密码授权?
- 是否明确 JWT 与内省的新鲜度、缓存、撤销、超时和故障行为?
- 用户身份与客户端身份是否可区分?
- 同意、Scope、角色、权益和对象授权是否分开管理?
- 是否只传播最小化已验证上下文?
- 是否移除调用方身份请求头并阻止后端直连绕过?
- 是否测试密钥轮换、同意撤回、客户端禁用和 CIAM 故障?
- 安全日志是否有用且不包含 Token 或过量客户数据?
常见问题
网关能否替代 CIAM?
不能。它可以执行 Token 与路由策略,但注册、认证旅程、账户恢复、身份联合、同意交互、客户生命周期和 Token 签发属于 CIAM 系统。
应用是否需要再次验证 Token?
这取决于信任边界。如果应用只能通过经过认证的网关链路访问,并接收最小化标准上下文,可能不必重复验证;如果 Token 会继续传递,或存在绕过路径,应用可能需要自行验证。应记录一套可辩护的模型。
Scope 能否替代对象级授权?
不能。Scope 描述面向某个受众的粗粒度权限,应用仍需执行账户、租户、所有权、工作流和数据状态规则。
后续步骤
使用 API 网关零信任架构构建身份边界,加入细粒度网关授权,并通过 API 访问日志审计保留证据。
