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

Keycloak 26中重写OIDCClientRegistrationProvider处理自定义字段

Keycloak 26扩展OIDC客户端注册自定义字段的解决方案

问题背景

在Keycloak 15中正常运行的自定义OIDC客户端注册扩展(用于处理请求体中的webhook_uris字段),升级到Keycloak 26后失效:请求仍进入内置的OIDCClientRegistrationProvider而非自定义实现,返回错误:

{
"error": "Invalid json representation for OIDCClientRepresentation. Unrecognized field "webhook_uris" at line 25 column 26."
}

已完成的前置操作:

  • 自定义了CustomOIDCClientRegistrationProviderFactory、CustomOIDCClientRegistrationProvider、CustomOIDCClientRepresentation类
  • 在resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory中注册了自定义SPI
  • 日志已验证SPI注册成功:

2025-08-05 18:38:19,821 WARN [org.key.services] (build-2) KC-SERVICES0047: openid-connect (com.connector.CustomOIDCClientRegistrationProviderFactory) is implementing the internal SPI client-registration. This SPI is internal and may change without notice

现有代码

CustomOIDCClientRegistrationProviderFactory.java

public class CustomOIDCClientRegistrationProviderFactory extends OIDCClientRegistrationProviderFactory {
    @Override
    public String getId() {
        return "openid-connect";
    }

    @Override
    public int order() {
        return 100;
    }

    @Override
    public OIDCClientRegistrationProvider create(KeycloakSession session) {
        return new CustomOIDCClientRegistrationProvider(session);
    }
}

CustomOIDCClientRegistrationProvider.java

public class CustomOIDCClientRegistrationProvider extends OIDCClientRegistrationProvider {

    public CustomOIDCClientRegistrationProvider(KeycloakSession session) {
        super(session);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createCustomOIDC(CustomOIDCClientRepresentation customOIDCClientRepresentation) {
        return Response.ok().entity(customOIDCClientRepresentation).build();
    }
}

CustomOIDCClientRepresentation.java

@JsonIgnoreProperties(ignoreUnknown = true)
public class CustomOIDCClientRepresentation extends OIDCClientRepresentation {

    @JsonProperty("webhook_uris")
    private String webhook_uris;

    public String getWebhook_uris() {
        return webhook_uris;
    }

    public CustomOIDCClientRepresentation setWebhook_uris(String webhook_uris) {
        this.webhook_uris = webhook_uris;
        return this;
    } 
}

修复步骤

Keycloak 26对client-registration SPI的加载逻辑和请求路由规则做了调整,需修改以下内容:

1. 调整自定义Factory的加载优先级

内置OIDCClientRegistrationProviderFactory的order()返回值为0,自定义Factory需要返回小于0的值才能优先被加载:

@Override
public int order() {
    return -1; // 比内置实现优先级更高
}

2. 重写父类的注册方法,而非新增方法

自定义Provider中新增的createCustomOIDC方法不会被框架路由,必须重写父类原有的create方法,并在方法内处理自定义字段:

public class CustomOIDCClientRegistrationProvider extends OIDCClientRegistrationProvider {

    public CustomOIDCClientRegistrationProvider(KeycloakSession session) {
        super(session);
    }

    @Override
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(OIDCClientRepresentation rep) {
        // 将内置Representation转换为自定义类型,解析webhook_uris字段
        ObjectMapper mapper = new ObjectMapper();
        CustomOIDCClientRepresentation customRep = mapper.convertValue(rep, CustomOIDCClientRepresentation.class);
        
        // 将自定义字段存储到客户端属性中(避免修改Keycloak内部实体结构)
        if (customRep.getWebhook_uris() != null) {
            if (rep.getAttributes() == null) {
                rep.setAttributes(new HashMap<>());
            }
            rep.getAttributes().put("webhook_uris", customRep.getWebhook_uris());
        }
        
        // 调用父类方法完成默认注册流程
        return super.create(rep);
    }
}

3. (可选)重写查询方法返回自定义字段

如果需要在查询客户端详情时返回webhook_uris,需重写get方法,将存储在属性中的值映射回自定义Representation:

@Override
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("id") String id) {
    Response originalResponse = super.get(id);
    OIDCClientRepresentation rep = (OIDCClientRepresentation) originalResponse.getEntity();
    
    CustomOIDCClientRepresentation customRep = new CustomOIDCClientRepresentation();
    // 拷贝原有字段
    BeanUtils.copyProperties(rep, customRep);
    // 从属性中读取自定义字段
    if (rep.getAttributes() != null) {
        customRep.setWebhook_uris(rep.getAttributes().get("webhook_uris"));
    }
    
    return Response.ok(customRep).build();
}

关键注意事项

  • Keycloak 20+版本调整了内部SPI的加载优先级规则,自定义Factory必须通过更低的order()值抢占内置实现的加载顺序
  • 不能仅新增自定义接口方法,必须重写父类中已有的对应HTTP方法(如create、update)才能拦截请求
  • 自定义字段建议存储在客户端的attributes集合中,避免直接修改Keycloak内部实体结构引发兼容性问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 13:44:50