如何为你的服务创建统一的授权中心
Posted on 一 20 7月 2026 in Tech
| Abstract | 如何为你的服务创建统一的授权中心 |
|---|---|
| Authors | Walter Fan |
| Category | Cloud / Security |
| Status | v1.0 |
| Updated | 2026-07-20 |
| License | CC-BY-NC-ND 4.0 |
为什么需要授权中心
服务少时,一句 if user.is_admin 很省事。服务多起来,权限会散落在代码、配置和网关里:改一条规则要发几个服务,查一次越权要翻半天日志,最后没人能准确回答“谁能调用谁”。
问题不在于少装了一个工具,而在于没有统一的策略来源。授权中心的价值,是集中管理策略,并让网关、Sidecar 或服务在合适的位置执行它。
下面先拆清身份、决策和执行,再讨论协议、实例与演进路线。
一、先拆清三个系统
微服务里的访问控制最好拆成三个关注点:
| 系统 | 回答的问题 | 典型实现 |
|---|---|---|
| Identity Provider | 你是谁? | Keycloak, Entra ID, Auth0, Okta |
| Authorization Center | 你是否有权限? | OPA, OpenFGA, Cedar, Casbin |
| Policy Enforcement Point(PEP) | 是否放行这次调用? | 服务中间件、Envoy、Istio、Kong |
Keycloak 也有授权能力,小系统完全可以先用;但当策略包含跨服务属性、动态条件或对象关系时,通常会把决策独立为 PDP(Policy Decision Point),由 OPA、OpenFGA 等组件承担。
二、调用流程:授权发生在哪一层?
以 Order Service 调用 Payment Service 退款为例:
User
│
│ Login (OIDC)
v
Keycloak ───► 签发 JWT Access Token
│
│ Token 随请求传递
v
Order Service
│
│ 发起退款请求(携带自身 Service Token)
v
Payment Sidecar ──► 拦截入站请求
│
│ 调用本地 OPA
│ 传入: subject=order-service, action=POST,
│ resource=/payment/refund, scope=payment.refund
v
OPA ──► 策略判定: Allow / Deny
│
├── Allow ──► Payment Service 正常处理
│
└── Deny ──► 返回 403,记录审计日志
网关和 Sidecar 适合检查身份、audience、Scope、路径等通用规则。涉及订单归属、余额或审批状态的判断仍要由业务服务提供属性,甚至留在业务层执行。把所有授权都塞进 Sidecar,只是把散落的 if 换了一个藏身之处。
三、授权中心应该管什么?
授权中心不能只管 Role。完整决策至少要能表达:
| 要素 | 含义 | 示例 |
|---|---|---|
| Subject | 谁在发起请求 | order-service, user:walter |
| Action | 要做什么操作 | POST, READ, DELETE |
| Resource | 操作的对象 | /payment/refund, doc:12345 |
| Condition | 附加条件 | region == us-west, time < 18:00 |
| Scope | OAuth2 作用域 | payment.write, payment.refund |
| Tenant | 租户隔离 | companyA, tenant-xyz |
| Environment | 环境限制 | production, staging |
| Attributes | 扩展属性 | risk_level: high, department: finance |
例如:
- subject: order-service
action: POST
resource: /payment/refund
condition:
region: us-west
amount_usd: { lte: 10000 }
scope:
- payment.write
- payment.refund
tenant: companyA
environment: production
effect: allow
这已经不是简单的 RBAC,而是基于属性的 ABAC。若还要表达“项目成员可以编辑项目下的文档”,则需要 ReBAC(关系型访问控制)。
四、OAuth2 Scope:微服务间授权的常用载体
Role 适合表达人的职责,如 Admin、Developer、Operator;服务身份更适合用 Scope 表达被授予的操作:
payment.read -- 读取支付信息
payment.write -- 创建支付
payment.refund -- 发起退款
JWT Access Token 里这样携带:
{
"iss": "https://auth.example.com",
"sub": "order-service",
"aud": "payment-service",
"exp": 1721473200,
"scope": "payment.read payment.write payment.refund"
}
Payment Service 同时验证签名、iss、aud、exp,再确认 Token 包含 payment.refund。只检查 Scope 不够。
Scope 命名最佳实践
{resource}.{action}
Examples:
payment.read
payment.write
inventory.query
order.cancel
user.profile.read -- 嵌套资源用 . 分隔
避免 admin、* 这类万能 Scope,也不要把多个动作绑成 read-write。
五、协议和组件怎么分工
不要把这些名字堆成“全家桶”。它们解决的是不同问题,按需选择即可。
| 层次 | 解决什么问题 | 常见选择 | 何时需要 |
|---|---|---|---|
| 用户认证 | 用户是谁 | OIDC;Keycloak、Entra ID、Auth0、Okta | 有用户登录时 |
| 服务取证 | 服务如何获得受限令牌 | OAuth2 Client Credentials | 服务间使用 Access Token 时 |
| 令牌格式 | 如何携带 sub、aud、exp、scope |
JWT(RFC 7519)或 opaque token | 需要跨组件传递授权上下文时 |
| 工作负载身份 | 当前 Pod/进程是否可信 | SPIFFE/SPIRE、X.509-SVID、mTLS | Kubernetes、多集群或零信任网络 |
| 属性策略 | 条件满足时是否允许 | OPA/Rego、Cedar、Casbin | 出现金额、地域、环境等条件时 |
| 关系授权 | 谁与哪个对象有什么关系 | OpenFGA | 文档共享、组织层级、对象级权限 |
| 策略执行 | 在哪里拦截请求 | 业务中间件、Gateway、Envoy/Istio | 所有系统都需要 |
几个边界要守住:
- ID Token 用来向客户端证明登录身份;调用 API 应使用 Access Token。
- JWT 不等于 OAuth2,也不是唯一 Token 格式。选择 JWT 意味着接受短期内难以即时撤销的权衡。
- SPIFFE 证明工作负载身份,不替代用户授权或业务权限。
- OPA 与 OpenFGA 不是必选组合。只有同时存在复杂条件和资源关系时,才值得一起上。
六、别让授权中心进入单点热路径
每次 RPC 都远程查询授权中心,中心一慢,所有服务跟着慢。对于 Scope 和属性策略,可以把 OPA Bundle 下发到本地执行:
Authorization Center (策略存储)
│
│ Bundle pull / periodic refresh
v
Envoy Sidecar (本地 OPA)
│
│ 本地策略评估
v
Business Logic
这种“中心管理、就近执行”能降低延迟,也能在控制面短暂故障时继续使用最后一版有效策略。具体延迟必须在真实策略和硬件上测试,不应拿“低于 1ms”当承诺。
关系授权不同。OpenFGA 的关系数据变化频繁,通常仍要查询服务端;缓存必须明确一致性窗口、失效方式和高风险操作的 fail-closed 策略。授权系统不可用时究竟拒绝、降级还是使用旧策略,也要按接口风险预先定义,不能临时拍脑袋。
七、Policy Store:策略也要走工程流程
策略可以是 YAML/JSON,也可以是 Rego。形式不重要,关键是可审查、可测试、可回滚。例如:
# 声明式格式(适合简单场景)
policies:
- id: order-can-refund
subject: order-service
resource: payment-service
action: POST
path: /payment/refund
scopes:
- payment.refund
conditions:
environment: production
amount_usd: { lte: 10000 }
effect: allow
| 能力 | 说明 |
|---|---|
| 版本管理 | 每次策略变更有版本号和变更人 |
| 审计日志 | 谁在什么时候改了什么策略 |
| 灰度发布 | 先 shadow,再 canary,最后全量 |
| 回滚 | 一键回到上个版本 |
| 测试 | 策略变更前可以跑单测和集成测试 |
| 影响分析 | 用历史请求回放,估算新增 allow/deny |
八、七个常见陷阱
| 陷阱 | 后果 | 改法 |
|---|---|---|
| 认证等于授权 | 登录成功就能访问过多资源 | 分开 AuthN 与 AuthZ |
服务拿 Admin Role |
一个凭证泄露即可横向移动 | 使用最小 Scope,并校验 aud |
admin、* 万能 Scope |
名义上有授权,实际上全放开 | 按 {resource}.{action} 拆分 |
| 每次远程鉴权 | PDP 故障拖垮业务链路 | 静态策略本地执行;远程决策设超时与降级规则 |
| 只记 allow,不记 deny | 无法发现探测和越权尝试 | 记录决策、原因、策略版本和 trace ID |
| 策略直接全量发布 | 一处拼写错误造成大面积 403 | lint → test → shadow → canary → rollout |
| JWT 活得太久 | 权限收回后 Token 仍有效 | 短期 Token;高风险操作使用实时决策或撤销机制 |
九、主流开源方案速查表
| 层次 | 推荐项目 | 核心能力 | 适用场景 |
|---|---|---|---|
| 身份认证 (IdP) | Keycloak | OAuth2, OIDC, SAML, User Federation | 统一用户/服务身份管理 |
| 策略授权 (ABAC) | Open Policy Agent (OPA) | Rego 语言, Bundle 分发, 可嵌入 | API 授权、K8s Admission、基础设施策略 |
| 关系授权 (ReBAC) | OpenFGA | Zanzibar 模型, 关系图, Check/ListObjects API | 文档共享、组织层级、多租户资源 |
| 通用策略引擎 | Cedar (AWS) | 类型化策略、Schema 和自动化分析工具 | 需要严格策略建模的场景 |
| 服务身份 | SPIFFE / SPIRE | X.509-SVID, JWT-SVID, Workload Attestation | K8s 环境下的 Zero Trust 服务身份 |
| Service Mesh | Istio | Envoy + mTLS + AuthorizationPolicy | 服务间通信的统一安全层 |
| API Gateway | Kong / APISIX | 认证插件 + Rate Limit + ACL | 南北向流量管控 |
十、一个典型实例:电商平台退款
用一个电商场景把前面的设计串起来。
业务场景
客服通过 API Gateway 发起退款,Order Service 再调用 Payment Service。规则只有四条:
- 只有 Order Service 能调用 Payment Service 的退款接口
- 单笔退款金额不能超过 10000 美元
- 只允许在生产环境的工作时间(9:00-18:00 UTC)内执行大额退款(>1000 美元)
- 所有退款操作必须有审计记录
架构图

时序图:退款请求的完整授权流程

对应的 OPA 策略(完整版)
package ecommerce.payment.authz
import rego.v1
default allow := false
# 公共条件只写一次,避免规则漂移。
valid_refund_request if {
input.subject == "order-service"
input.action == "POST"
input.resource == "/payment/refund"
input.audience == "payment-service"
"payment.refund" in input.scopes
input.attributes.amount_usd > 0
}
# 小额退款不受工作时间限制。
allow if {
valid_refund_request
input.attributes.amount_usd <= 1000
}
# 大额退款只能在生产环境的工作时间执行。
allow if {
valid_refund_request
input.attributes.amount_usd > 1000
input.attributes.amount_usd <= 10000
input.environment == "production"
input.context.hour_utc >= 9
input.context.hour_utc < 18
}
allow if {
input.subject == "order-service"
input.action == "GET"
startswith(input.resource, "/payment/")
input.audience == "payment-service"
"payment.read" in input.scopes
}
这段策略默认拒绝,只开放两条明确路径。时间由请求上下文传入,便于测试和回放;PEP 还应独立验证 Token 签名、iss、exp,不要把所有责任都压给 Rego。
审计日志格式
每次授权决策产生一条结构化日志:
{
"timestamp": "2026-07-20T14:30:05.123Z",
"decision": "allow",
"subject": "order-service",
"action": "POST",
"resource": "/payment/refund",
"scopes": ["payment.refund"],
"attributes": {
"amount_usd": 3000,
"order_id": "12345",
"triggered_by": "user:cs-agent-01"
},
"environment": "production",
"policy_version": "v2.3.1",
"evaluation_time_ms": 0.4,
"spiffe_id": "spiffe://example.com/ns/production/sa/order-service",
"trace_id": "abc123def456"
}
这条日志能串起操作人、调用服务、业务对象、策略版本和 Trace。注意不要记录 Token、密钥或不必要的个人数据。
十一、技术演进路线图:按问题升级,不按名词堆料
下面的人数和服务数只是参照,不是门槛。真正的升级信号,是现有方案已经无法控制风险和变更成本。
| 阶段 | 典型组织 | 够用的方案 | 升级信号 |
|---|---|---|---|
| Level 0 | 创业团队;单体或 1-3 个服务 | 框架中间件 + 简单 RBAC | 出现部门、资源归属等条件 |
| Level 1 | 成长团队;约 5-15 个服务 | 统一 IdP + JWT Scope + API Gateway | 策略变更频繁、需要集中审计 |
| Level 2 | 平台团队;约 15-50 个服务 | OPA + Policy-as-Code + 集中审计;按需引入 SPIFFE/Istio | 出现多租户、共享关系和对象级权限 |
| Level 3 | 大型平台;多团队、多地域 | OPA + OpenFGA + 多集群身份 + 完整治理 | 已进入持续治理阶段 |
Level 0:先把权限收口
使用 Spring Security、Django permission 等框架能力即可。认证和授权分开,权限判断集中在 middleware 或 decorator,避免散落在业务函数里。两三个角色能解决问题,就不要提前建设“平台”。
当规则开始出现“只能看自己部门的数据”或“只能修改自己创建的资源”,简单 RBAC 已经吃力,应进入下一阶段。
Level 1:统一身份和 Scope
引入统一 IdP。服务通过 Client Credentials 获取短期 Access Token,并验证签名、iss、aud、exp 和 Scope。公共验证逻辑做成中间件;Gateway 负责外部流量的粗粒度检查,服务仍对自己的资源负责。
这一阶段最重要的产物不是 Keycloak 集群,而是一套稳定的 Scope 命名和最小权限规则。若改策略必须发布多个服务,或安全团队已无法回答“谁能访问什么”,再进入 Level 2。
Level 2:把策略从服务中拿出来
用 OPA 等 PDP 承载跨服务的属性策略,策略进入 Git,变更经过测试、shadow、canary 和回滚。决策日志集中保存。Kubernetes 环境若确实需要统一工作负载身份和 mTLS,再引入 SPIFFE/SPIRE;只有已经具备平台团队时,才考虑 Istio。
这一级的关键变化,是授权从业务代码变成平台能力,但资源属性仍由业务服务负责。出现文档共享、组织继承、多租户对象关系时,再引入 ReBAC。
Level 3:治理复杂关系和跨地域风险
在 OPA 处理条件策略的基础上,用 OpenFGA 管对象关系;用多集群身份体系解决跨地域信任。平台还应提供策略影响分析、租户隔离、限时 Break-Glass、权限矩阵和 SIEM 对接。
到了这一级,难点已不是“选 OPA 还是 OpenFGA”,而是数据一致性、策略所有权和跨团队治理。
演进原则
- 按痛点升级:没有复杂关系,就不要上 OpenFGA;没有平台团队,就慎上 Istio。
- 先统一语义:Subject、Resource、Action、Scope 和 Tenant 的定义,比工具选型更早。
- 默认拒绝:新增资源必须显式授权;高风险接口故障时 fail closed。
- 保留退出路径:策略、关系数据和审计格式不要被某个产品私有模型锁死。
- 把撤权当主流程:授予容易,及时回收、Token 失效和权限盘点更重要。
十二、参考资料
核心协议和标准
- RFC 6749 - OAuth 2.0 Authorization Framework
- RFC 7519 - JSON Web Token (JWT)
- RFC 8725 - JWT Best Current Practices
- RFC 6750 - Bearer Token Usage
- OpenID Connect Core 1.0
- SPIFFE Specification
- Google Zanzibar Paper (2019)
开源项目
- Open Policy Agent (OPA) — 通用策略引擎
- OpenFGA — 关系型授权引擎(Zanzibar 实现)
- Keycloak — 开源 Identity & Access Management
- SPIRE — SPIFFE 运行时实现
- Cedar — AWS 开源的策略语言,支持形式化验证
- Casbin — 轻量级通用授权库
相关架构模式
最后一句
授权这件事,本质上是在回答一个组织治理问题:谁应该信任谁,到什么程度。 技术只是把这个问题的答案编码成可执行的策略。
统一授权中心不是让你写一个"万能的权限网关",而是建立一套"策略管理的单一事实源"——策略在哪里定义、谁能修改、怎么分发、出了问题怎么回滚,这些流程清楚了,技术选型反而是最简单的那一步。
本文是授权系列的第三篇,前两篇: - 授权的领域模型:从 RBAC、ABAC 到 Keycloak、Vault 的一张全景图 - 用开源组件搭一个 AWS IAM 风格的授权系统
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。