惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Martin Fowler
Martin Fowler
Last Week in AI
Last Week in AI
罗磊的独立博客
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
V
Visual Studio Blog
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网
博客园_首页
人人都是产品经理
人人都是产品经理
量子位
美团技术团队
The Cloudflare Blog
小众软件
小众软件
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
博客园 - Franky

博客园 - 张占岭

keycloak~缓存对象的介绍与作用 wso2-apim-mcp2改版分析 mcp1和mcp2对比 为什么前端项目使用ts比js要多了 多个IDP的用户联合方案的分析 apisix~forward-auth外部认证 apisix~response-rewrite响应头重写 springboot~关于构造方法注入和字段注入的选择 apisix~OpenResty各阶段的介绍 wso2~关于workbuddy中mcp在wso2中的授权端点 k8s~避免调度资源碎片化 WEB安全~csrf介绍 WEB安全~DPoP的介绍 WEB安全~csp的介绍 什么是中间人攻击 WEB安全~xss的介绍 k8s~pod资源限制和JVM的XMX配置 关于对wso2和keycloak的token交换的调研 keycloak~实现OAuth 2.0 Token Exchange backstage~实体的介绍及它们的关系 backstage~openapi的接入与protobuf的对比 backstage~将java服务添加到backstage backstage~对接github和gitlab backstage~开始一个backstage应用 apisix~graphQL的支持 wso2几个核心项目介绍 docker~BuildKit的介绍 k8s~secret资源的使用 算法~时间戳计算两个日期是否为同天同时同分 wso2~4.5升级到4.6需要更新的数据表
keycloak~aud受众字段的作用及如何生成
张占岭 · 2026-06-04 · via 博客园 - 张占岭

在OAuth 2.0框架中,aud(受众)声明的核心功能是明确指定令牌的合法接收者,它是一个关键的安全验证机制

⚙️ 核心原则:标识与验证

具体来说,这个机制遵循以下原则:

  • 标识:授权服务器在签发令牌时,会将目标API的唯一标识写入aud字段,明确告知客户端“这个令牌是发给谁用的”。
  • 验证:API在收到令牌后,必须验证aud字段的值是否与自身的标识(如https://api.my-api.com或客户端ID)相匹配。如果不匹配,则必须拒绝该令牌。由于aud可能为字符串数组,此时只需确保API自身的标识至少包含在其中一项即可。

虽然aud验证很关键,但它不能作为唯一的授权检查,还应结合角色(roles)、权限范围(scopes)等其他声明进行多层授权校验。

📌 在不同场景下的使用细节

aud字段的具体使用在不同场景下有所不同:

  • 访问令牌 (Access Token)aud标识该令牌可访问的目标API。在OAuth 2.0核心规范中,令牌通常是“不透明白字符串”,aud字段是随着JWT格式令牌的普及才变得常见。客户端通过audienceresource参数向授权服务器请求特定API的令牌。
  • ID令牌 (ID Token):在基于OAuth 2.0的OpenID Connect中,aud声明是必需(REQUIRED) 字段。它必须包含客户端的client_id,用于令牌接收方(客户端)确认令牌是“颁发给自己的”,防止被其他客户端滥用。
  • 客户端断言 (Client Assertion):用于客户端向授权服务器证明自己身份。此场景下的JWT中的aud(受众)字段,必须设为授权服务器的令牌端点URL(如https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token)。例如,在客户端凭证模式中使用private_key_jwt认证方式时,JWT断言的aud值就必须指向授权服务器的令牌端点。
  • 令牌内省 (Token Introspection):授权服务器的内省端点会返回令牌的元数据,其中包含aud字段。资源服务器可以利用此信息对令牌进行更严格的验证,例如确认aud是否包含自身的资源标识符。
  • 资源指示器 (RFC 8707):通过引入resource参数(取代非标准的audience参数),允许客户端在一个授权请求中为多个API请求单独的令牌,避免产生一个能访问多项服务的“超级令牌”,以此将潜在的安全风险降至最低。

🚫 常见配置误区

在实际配置中,一个常见的错误是混淆audclient_id。例如,将访问令牌的aud(应指API)错误地设置成了客户端的client_id,这将导致API因无法匹配自身标识而拒绝令牌。

keycloak中的aud如何生成

  • 位于:org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper
  • 作用:从当前用户的客户端角色中,将客户端client_id提取出来,放到aud字段里,它是一个数组类型

原码

/**
 * Protocol mapper, which adds all client_ids of "allowed" clients to the audience field of the token. Allowed client means the client
 * for which user has at least one client role
 *
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public class AudienceResolveProtocolMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {

    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();


    public static final String PROVIDER_ID = "oidc-audience-resolve-mapper";


    public List<ProviderConfigProperty> getConfigProperties() {
        return configProperties;
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }

    @Override
    public String getDisplayType() {
        return "Audience Resolve";
    }

    @Override
    public String getDisplayCategory() {
        return TOKEN_MAPPER_CATEGORY;
    }

    @Override
    public String getHelpText() {
        return "Adds all client_ids of \"allowed\" clients to the audience field of the token. Allowed client means the client\n" +
                " for which user has at least one client role";
    }

    @Override
    public int getPriority() {
        return ProtocolMapperUtils.PRIORITY_AUDIENCE_RESOLVE_MAPPER;
    }

    @Override
    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
                                            UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
        String clientId = clientSessionCtx.getClientSession().getClient().getClientId();

        for (Map.Entry<String, AccessToken.Access> entry : RoleResolveUtil.getAllResolvedClientRoles(session, clientSessionCtx).entrySet()) {
            // Don't add client itself to the audience
            if (entry.getKey().equals(clientId)) {
                continue;
            }

            AccessToken.Access access = entry.getValue();
            if (access != null && access.getRoles() != null && !access.getRoles().isEmpty()) {
                token.addAudience(entry.getKey());
            }
        }

        return token;
    }

    public static ProtocolMapperModel createClaimMapper(String name) {
        ProtocolMapperModel mapper = new ProtocolMapperModel();
        mapper.setName(name);
        mapper.setProtocolMapper(PROVIDER_ID);
        mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
        mapper.setConfig(Collections.emptyMap());
        return mapper;
    }
}