You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Keycloak请求服务账户Token时传递自定义参数到JWT

在Keycloak Client Credentials模式下添加自定义请求参数到JWT Claim

需求场景

通过client_credentials模式请求Access Token时,传递自定义表单参数(如foo=bar),使该参数成为JWT中的Claim:

$ http --form --auth myclient:mysecret POST http://localhost:7070/realms/test/protocol/openid-connect/token \
  grant_type=client_credentials \
  foo=bar

期望JWT包含:

{
  "iss": "http://localhost:7070/auth/realms/test",
  ...
  "clientId": "myclient",
  "foo": "bar"
}

已尝试但无效的方案

  • 自定义请求表单参数被Keycloak直接忽略
  • 自定义Scope返回"Invalid scope"错误
  • 启用实验性Dynamic Scopes并配置foo:*未生效
  • 基于AbstractOIDCProtocolMapper的Java扩展无法直接获取请求的表单/查询参数

可行解决方案:自定义Grant Handler + Protocol Mapper

Keycloak默认不会保留token端点的自定义参数,需通过扩展Grant Handler捕获参数并存储,再通过Protocol Mapper将存储的参数注入JWT。

步骤1:实现自定义Client Credentials Grant Handler

该Handler会拦截client_credentials类型的token请求,提取自定义参数并存储到Client Session的notes中:

import org.keycloak.OAuth2Constants;
import org.keycloak.models.ClientSession;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.protocol.oidc.grants.AbstractOAuth2GrantHandler;
import org.keycloak.protocol.oidc.grants.OAuth2Request;
import org.keycloak.services.resources.Response;

import javax.ws.rs.core.MultivaluedMap;

public class CustomClientCredentialsGrant extends AbstractOAuth2GrantHandler {

    @Override
    public Response createAccessToken(OAuth2Request request) {
        // 获取请求中的自定义表单参数
        MultivaluedMap<String, String> formParams = request.getFormParameters();
        String fooValue = formParams.getFirst("foo");
        
        // 获取当前客户端会话
        ClientSessionContext clientSessionCtx = request.getClientSessionContext();
        ClientSession clientSession = clientSessionCtx.getClientSession();
        
        // 将自定义参数存入会话note(可支持多个参数,此处以foo为例)
        if (fooValue != null) {
            clientSession.setNote("custom_foo", fooValue);
        }
        
        // 调用父类逻辑生成Token
        return super.createAccessToken(request);
    }

    @Override
    public String getGrantType() {
        // 覆盖默认的client_credentials grant type
        return OAuth2Constants.CLIENT_CREDENTIALS;
    }
}

步骤2:实现自定义Protocol Mapper

该Mapper从Client Session的notes中读取参数,添加到JWT的Claim中:

import org.keycloak.models.ClientSession;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSession;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCToken;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.protocol.oidc.mappers.ProtocolMapper;

import java.util.ArrayList;
import java.util.List;

public class CustomSessionNoteClaimMapper extends AbstractOIDCProtocolMapper implements ProtocolMapper {

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

    static {
        // 配置项:Claim名称
        ProviderConfigProperty claimNameProp = new ProviderConfigProperty();
        claimNameProp.setName("claim_name");
        claimNameProp.setLabel("Claim 名称");
        claimNameProp.setType(ProviderConfigProperty.STRING_TYPE);
        claimNameProp.setHelpText("要添加到Token中的Claim名称");
        CONFIG_PROPERTIES.add(claimNameProp);

        // 配置项:Session Note的Key
        ProviderConfigProperty noteKeyProp = new ProviderConfigProperty();
        noteKeyProp.setName("note_key");
        noteKeyProp.setLabel("客户端会话Note的Key");
        noteKeyProp.setType(ProviderConfigProperty.STRING_TYPE);
        noteKeyProp.setHelpText("存储自定义参数的Session Note键名");
        CONFIG_PROPERTIES.add(noteKeyProp);
    }

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

    @Override
    public String getDisplayType() {
        return "自定义客户端会话Note Claim";
    }

    @Override
    public String getHelpText() {
        return "将客户端会话Note中的值作为Claim添加到Token中";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return CONFIG_PROPERTIES;
    }

    @Override
    public String getId() {
        return "custom-session-note-claim-mapper";
    }

    @Override
    protected void setClaim(OIDCToken token, ProtocolMapperModel mappingModel, UserSession userSession,
                            org.keycloak.models.KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
        String claimName = mappingModel.getConfig().get("claim_name");
        String noteKey = mappingModel.getConfig().get("note_key");

        if (claimName == null || noteKey == null) {
            return;
        }

        ClientSession clientSession = clientSessionCtx.getClientSession();
        String claimValue = clientSession.getNote(noteKey);

        if (claimValue != null) {
            token.getOtherClaims().put(claimName, claimValue);
        }
    }
}

步骤3:打包与部署

  1. 创建Maven项目,添加Keycloak对应版本的依赖(以16.1.1为例):
<dependencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-core</artifactId>
        <version>16.1.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi</artifactId>
        <version>16.1.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi-private</artifactId>
        <version>16.1.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-services</artifactId>
        <version>16.1.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 打包成jar文件,放入Keycloak的providers目录。
  2. 重启Keycloak:
    • Keycloak 16:执行./standalone.sh(或对应环境的启动脚本)
    • Keycloak 20+:执行./kc.sh build && ./kc.sh start

步骤4:客户端配置

  1. 进入目标客户端的Mappers页面,点击Create。
  2. 在Mapper Type中选择刚才创建的自定义客户端会话Note Claim。
  3. 配置参数:
    • Claim 名称:填写foo
    • 客户端会话Note的Key:填写custom_foo
  4. 保存配置,确保客户端已启用client_credentials授权类型。

验证

重新发送测试请求,解析返回的Access Token,即可看到foo: "bar"已加入JWT的Claims中。

内容的提问来源于stack exchange,提问作者lathspell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 11:45:36