Keycloak中实现自定义SPI

Keycloak SPI

Keycloak提供了强大的插件功能,这些插件通过SPI(Service Provider Interface)的方式集成到keycloak中。

SPI是扩展系统的一种通用方案,它相当于一个接口规范,要求实现者必须按照接口定义来实现方法,而系统里面则会调用SPI的方法来实现系统功能。

Keycloak提供了众多的SPI,供用户根据自己业务需要进行扩展和实现。比如:

  • ThemeSelectorProviderFactory,用于实现自定义的主体选择器
  • LocaleSelectorProviderFactory,用于实现自定义的国际化选择器
  • RealmResourceProviderFactory,用于实现自定义的REST接口
  • JpaEntityProviderFactory,用于扩展JPA的entity

除此之外,keycloak还允许定义自己的SPI。这些自定义SPI通常可以被REST的自定义Provider配合使用,来达到一定的业务目的。

本文的重点,就是演示如何自定义SPI,如何让keycloak使用它,以及如何通过这个SPI来扩展keycloak的功能。

自定义SPI示例

定义SPI的接口

首先,需要定义ProviderFacotry和Provider的子接口,表示你的SPI需要实现的功能。

1)定义Provider接口

这个接口里面的方法,就是你要暴露给使用者的功能。

java 复制代码
package com.dadaer.keycloak.example.spi;

import org.keycloak.provider.Provider;

public interface ExampleServiceProvider extends Provider {
	// 定义你的SPI需要暴露的功能,这里使用sayHi举例
    String sayHi(String name);
}

2)定义ProviderFacotry接口

java 复制代码
package com.dadaer.keycloak.example.spi;

import org.keycloak.provider.ProviderFactory;

public interface ExampleServiceProviderFactory extends ProviderFactory<ExampleServiceProvider> {
}

声明SPI接口

定义完了SPI接口,需要通知Keycloak,让它知道这些SPI。因此需要编写一个实现了org.keycloak.provider.Spi的类,如下:

java 复制代码
package com.dadaer.keycloak.example.spi;

import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;

public class ExampleSpi implements Spi {
    @Override
    public boolean isInternal() {
        return false;
    }

    @Override
    public String getName() {
        // 定义SPI的名字为example
        return "example";
    }

    @Override
    public Class<? extends Provider> getProviderClass() {
        // 声明Provider接口是ExampleServiceProvider
        return ExampleServiceProvider.class;
    }

    @Override
    public Class<? extends ProviderFactory<ExampleServiceProvider>> getProviderFactoryClass() {
    	// 声明ProviderFactory接口是ExampleServiceProviderFactory
        return ExampleServiceProviderFactory.class;
    }
}

同样需要通知keycloak这个实现类,方式是在META-INF/services目录下增加一个叫做org.keycloak.provider.Spi的文件,然后写入如下内容:

com.dadaer.keycloak.example.spi.ExampleSpi

使用SPI

在声明了自定义的SPI接口以后,我们需要使用它。这里通过扩展REST API的方式来使用(这种方式也是常见的使用自定义SPI的方式)。

1)定义一个RealmResourceProvider实现类

java 复制代码
package com.dadaer.keycloak.example.rest;

import com.dadaer.keycloak.example.spi.ExampleServiceProvider;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resource.RealmResourceProvider;

public class MyRestProvider implements RealmResourceProvider {

    private final KeycloakSession session;

    public MyRestProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public Object getResource() {
        return this;
    }

    @Override
    public void close() {

    }

    @GET
    @Path("")
    @Produces(MediaType.TEXT_PLAIN)
    public String sayHi(@QueryParam("name") String name) {
        // 这里通过KeycloakSession来获取SPI的实例,然后调用其上的方法
        ExampleServiceProvider example = session.getProvider(ExampleServiceProvider.class);
        return example.sayHi(name);
    }

}

这里的重点是,通过KeycloakSession#getProvider方法来获取我们自定义好的SPI,然后调用它上面的方法。

2)定义一个RealmResourceProviderFactory的实现

java 复制代码
package com.dadaer.keycloak.example.rest;

import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;

public class MyRestProviderFactory implements RealmResourceProviderFactory {

    private final static String PROVIDER_ID = "example-rest";

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

    @Override
    public RealmResourceProvider create(KeycloakSession session) {
        return new MyRestProvider(session);
    }

    // 省略其他方法...
}

3)通知keycloak这个SPI实现 在META-INF/services目录下,新建一个叫做org.keycloak.services.resource.RealmResourceProviderFactory的文件,内容如下:

com.dadaer.keycloak.example.rest.MyRestProviderFactory

实现SPI

在定义和声明完自定义SPI以后,我们可以去实现它。

1)实现MyExampleServiceProvider

java 复制代码
package com.dadaer.keycloak.example.impl;

import com.dadaer.keycloak.example.spi.ExampleServiceProvider;
import org.keycloak.models.KeycloakSession;

public class MyExampleServiceProvider implements ExampleServiceProvider {

    private final KeycloakSession session;

    public MyExampleServiceProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public String sayHi(String name) {
        return "Hello, " + name + " from " + session.getContext().getRealm().getName();
    }

    @Override
    public void close() {

    }
}

这里,我们实现了SPI中定义的sayHi方法。

2)实现ExampleServiceProviderFactory接口

java 复制代码
package com.dadaer.keycloak.example.impl;

import com.dadaer.keycloak.example.spi.ExampleServiceProvider;
import com.dadaer.keycloak.example.spi.ExampleServiceProviderFactory;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class MyExampleServiceProviderFactory implements ExampleServiceProviderFactory {

    private static final String PROVIDER_ID = "my-spi-example";

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

    @Override
    public ExampleServiceProvider create(KeycloakSession keycloakSession) {
        return new MyExampleServiceProvider(keycloakSession);
    }
	// 省略其他方法...
}

可以看到,这里最主要实现的方法是getIdcreate,分别返回唯一id和具体的Provider实现类。

3)通知keycloak这个SPI实现 在META-INF/services目录下,新增一个叫做com.dadaer.keycloak.example.impl.MyExampleServiceProviderFactory的文件,内容如下:

rust 复制代码
com.dadaer.keycloak.example.impl.MyExampleServiceProviderFactory

测试

在把上面的步骤执行完以后,打包生成一个jar文件,放到keycloak的providers目录,然后重启keycloak。

bash 复制代码
# 打包
mvn clean package

# 把生成的jar文件拷贝到keycloak的providers目录
cp -f target/custom-spi.jar $KEYCLOAK_HOME/providers

# 重启keycloak
cd $KEYCLOAK_HOME
./bin/kc.sh start-dev --http-port=8180

通过下面的命令来测试结果:

bash 复制代码
curl http://localhost:8180/realms/master/example-rest?name=Jimmy

总结

Keycloak支持以SPI的方式来扩展功能,甚至可以让用户自定义SPI。自定义SPI的大致步骤为:

  • 定义SPI接口,指定提供的功能和方法
  • 通过org.keycloak.provider.Spi来声明自定义SPI接口
  • 在Keycloak中使用SPI接口定义的方法
  • 实现自定义的SPI接口
相关推荐
测开小菜鸟11 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天1 小时前
java的threadlocal为何内存泄漏
java
caridle1 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^1 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花2 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端2 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan2 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源