Audience 白名单
一句话定义:allowed audiences 是 Auth Server 侧给每个 Client 配置的“audience 白名单”——规定这个 Client 最多只能拿到指向哪些 Resource Server 的 Token。它是 Token 签发环节的第一道闸门。
一、它在整条链路里的位置
理解它需要把三个层次的"audience"区分开,它们是一条链上的三环:
#mermaid-svg-LwssnMnbQeyxtJSc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LwssnMnbQeyxtJSc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LwssnMnbQeyxtJSc .error-icon{fill:#552222;}#mermaid-svg-LwssnMnbQeyxtJSc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LwssnMnbQeyxtJSc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LwssnMnbQeyxtJSc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LwssnMnbQeyxtJSc .marker.cross{stroke:#333333;}#mermaid-svg-LwssnMnbQeyxtJSc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LwssnMnbQeyxtJSc p{margin:0;}#mermaid-svg-LwssnMnbQeyxtJSc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LwssnMnbQeyxtJSc .cluster-label text{fill:#333;}#mermaid-svg-LwssnMnbQeyxtJSc .cluster-label span{color:#333;}#mermaid-svg-LwssnMnbQeyxtJSc .cluster-label span p{background-color:transparent;}#mermaid-svg-LwssnMnbQeyxtJSc .label text,#mermaid-svg-LwssnMnbQeyxtJSc span{fill:#333;color:#333;}#mermaid-svg-LwssnMnbQeyxtJSc .node rect,#mermaid-svg-LwssnMnbQeyxtJSc .node circle,#mermaid-svg-LwssnMnbQeyxtJSc .node ellipse,#mermaid-svg-LwssnMnbQeyxtJSc .node polygon,#mermaid-svg-LwssnMnbQeyxtJSc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LwssnMnbQeyxtJSc .rough-node .label text,#mermaid-svg-LwssnMnbQeyxtJSc .node .label text,#mermaid-svg-LwssnMnbQeyxtJSc .image-shape .label,#mermaid-svg-LwssnMnbQeyxtJSc .icon-shape .label{text-anchor:middle;}#mermaid-svg-LwssnMnbQeyxtJSc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LwssnMnbQeyxtJSc .rough-node .label,#mermaid-svg-LwssnMnbQeyxtJSc .node .label,#mermaid-svg-LwssnMnbQeyxtJSc .image-shape .label,#mermaid-svg-LwssnMnbQeyxtJSc .icon-shape .label{text-align:center;}#mermaid-svg-LwssnMnbQeyxtJSc .node.clickable{cursor:pointer;}#mermaid-svg-LwssnMnbQeyxtJSc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LwssnMnbQeyxtJSc .arrowheadPath{fill:#333333;}#mermaid-svg-LwssnMnbQeyxtJSc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LwssnMnbQeyxtJSc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LwssnMnbQeyxtJSc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LwssnMnbQeyxtJSc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LwssnMnbQeyxtJSc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LwssnMnbQeyxtJSc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LwssnMnbQeyxtJSc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LwssnMnbQeyxtJSc .cluster text{fill:#333;}#mermaid-svg-LwssnMnbQeyxtJSc .cluster span{color:#333;}#mermaid-svg-LwssnMnbQeyxtJSc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LwssnMnbQeyxtJSc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LwssnMnbQeyxtJSc rect.text{fill:none;stroke-width:0;}#mermaid-svg-LwssnMnbQeyxtJSc .icon-shape,#mermaid-svg-LwssnMnbQeyxtJSc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LwssnMnbQeyxtJSc .icon-shape p,#mermaid-svg-LwssnMnbQeyxtJSc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LwssnMnbQeyxtJSc .icon-shape .label rect,#mermaid-svg-LwssnMnbQeyxtJSc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LwssnMnbQeyxtJSc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LwssnMnbQeyxtJSc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LwssnMnbQeyxtJSc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
✅ 通过
❌ 不在白名单
① Client 注册表allowed audiences(AS 侧静态配置)
② Token 请求resource 参数(RFC 8707)
AS 校验:请求的 audience∈ allowed audiences?
③ 签发 Tokenaud claim = 请求的 audience
拒绝invalid_target
④ RS 验签时校验aud == 自己
| allowed audiences | Client 注册时在 AS 配置的白名单,声明“这个 Client 被允许为哪些 RS 换取 Token" | admin-bff-client → [admin-api, order-api, crm-api] |
| resource 参数 | Client 每次发起授权/Token 请求时动态指定想要的 audience(RFC 8707 Resource Indicators) | resource=https://api.fdi.com/order-api |
| aud claim | AS 签发的 JWT 里的字段,声明“这张 Token 是发给谁的" | "aud": "https://api.fdi.com/order-api" |
| 流程串起来就是:请求时带 resource → AS 先查 Client 的 allowed audiences 白名单 → 通过才签发对应 aud 的 Token → RS 收到后校验 aud 是否是自己。 |
二、本架构里各 Client 的白名单实例
| gateway-bff-client | order-api, crm-api | gateway 前端聚合的业务就这两个域 |
| admin-bff-client | admin-api, order-api, crm-api | admin 自己的 API + 张三查订单场景需要的业务数据域 |
| gateway-m2m-client | 仅 open-api | 机器身份只调 admin 开放 API,不允许碰业务服务 |
| demo-service-client | 仅 order-api(或按需含 crm-api) | 第三方最小授权 |
| crm-m2m-client(上一轮新增) | 仅 open-api | crm 只取 admin 的系统级数据 |
| 一个反例说明价值:如果 demo-service-client 的 Token 请求里写 resource=https://api.fdi.com/admin-api,AS 查白名单发现不含此项 → 直接返回 invalid_target 错误,Token 根本签不出来。没有这道闸门的话,任何 Client 只要能通过认证,理论上就能请求指向任意服务的 Token——audience 隔离的防线就只剩 RS 自己的 aud 校验一层了。 |
三、它和 scope 是什么关系
两者是两个维度的正交控制,共同构成签发时的权限判定:
最终 Token 权限 = audience 匹配(allowed audiences ∩ resource 参数)
× scope 匹配(client 注册 scopes ∩ 用户授权 scope)
- audience 回答“这张 Token 能打到哪个服务”——空间边界
- scope 回答“这张 Token 到了那里能干什么”——行为边界
类比:audience 是“通行证允许进哪栋楼”,scope 是“进了楼之后允许动哪些房间”。只有楼对(aud 校验过)且房间对(scope 校验过)才放行。
四、实现层面的说明
“allowed audiences” 这个字段名不是 OAuth 标准里的固定叫法,是各 AS 实现里的常见概念:
| Keycloak | Client Scope + Audience Mapper(给 client 配置可获得的 audience) |
| Spring Authorization Server | 无内置字段,需自定义——在 OAuth2TokenCustomizer 或请求校验器里,比对请求的 resource/audience 参数与注册表配置,不匹配则拒绝 |
| Auth0 | “API 授权”:给 client 授权可访问哪些 API(每个 API 就是一个 audience) |
| 标准依据是 RFC 8707(Resource Indicators for OAuth 2.0):请求用 resource 参数指定目标 RS,AS 必须把签发 Token 的 aud 绑定到该 resource,并建议 AS 做客户端级的 resource 白名单校验。本文档里的 allowed audiences 就是这个“建议”的落地形式。 |
五、小结
- JWT 的 aud claim:Token 上写的“发给谁”,RS 据此拒绝转发来的别家 Token——这是收货方的校验。
- allowed audiences:AS 上配的“这个 Client 只许要谁家的 Token”——这是发货方的校验。
- 两道闸门前后夹击:即使某个 Client 被攻破或配置失误,它也拿不到白名单之外服务的 Token;即使有人截获 Token 想投递到别处,RS 的 aud 校验也拒收。收发两端都封死,audience 隔离才算真正闭环。
网硕互联帮助中心







评论前必须登录!
注册