Keycloak中新增实体(Entity)

Keycloak关于JPA的SPI

关于扩展Keycloak SPI的文章

我们可以通过SPI来向Keycloak添加JPA的Entity,来满足我们的业务需求。本示例根据官方的examples改编而来,目的是更加清晰地展示如何自定义JPA的Entity。

该示例中,定义了一个新的实体(Entity)- Company,用来保存公司信息。为了把它加入到keycloak中,需要实现JpaEntityProvider以及JpaEntityProviderFactory这两个接口。

然后,定义了一个REST API(从RealmResourceProviderFactory而来),来通过JPA的EntityManager实现对Company实体的CRUD。

扩展JPA实体示例

实现JPA的SPI

定义实体对象

实体对象是符合JPA规范的POJO,即:具有特定JPA注解的Java类。以下是本示例要用到的实体。

java 复制代码
package com.dadaer.keycloak.jpa.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

@Entity
@Table(name = "EXAMPLE_COMPANY")
@NamedQueries({@NamedQuery(name = "findByRealm", query = "from Company where realmId = :realmId")})
public class Company {

    @Id
    @Column(name = "ID")
    private String id;

    @Column(name = "NAME", nullable = false)
    private String name;

    @Column(name = "REALM_ID", nullable = false)
    private String realmId;

    // getters and setters...
}

实现JpaEntityProvider接口

java 复制代码
package com.dadaer.keycloak.jpa;

import com.dadaer.keycloak.jpa.entity.Company;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.models.KeycloakSession;

import java.util.Collections;
import java.util.List;

public class CompanyJapEntityProvider implements JpaEntityProvider {

    private final KeycloakSession session;

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

    /**
     * 返回所有需要管理的实体(Entity),这里只有一个Company实体
     */
    @Override
    public List<Class<?>> getEntities() {
        return Collections.singletonList(Company.class);
    }

    /**
     * 指定Liquibase的变更记录文件的位置,这里表示直接放在了classpath下面
     */
    @Override
    public String getChangelogLocation() {
        return "example-changelog.xml";
    }

    @Override
    public String getFactoryId() {
        return CompanyJpaEntityProviderFactory.PROVIDER_ID;
    }

    @Override
    public void close() {
    }
}

这里需要注意两个点:

  • 通过getEntities方法指定需要操作的实体集合
  • 通过getChangelogLocation方法指定Liquibase变更记录文件的问题

Keycloak使用Liquibase作为数据库版本控制的工具,所以需要提供一个Liquibase的changelog文件,通常这个文件是xml格式,但是也支持JSON和YAML等格式。(这里用的是xml格式)。

实现JpaEntityProviderFactory接口

java 复制代码
package com.dadaer.keycloak.jpa;

import org.keycloak.Config;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class CompanyJpaEntityProviderFactory implements JpaEntityProviderFactory {

    final static String PROVIDER_ID = "company-jpa-provider";

    @Override
    public JpaEntityProvider create(KeycloakSession session) {
        return new CompanyJapEntityProvider(session);
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
    
    // 其他方法...
}

增加Liquibase的changelog文件

在resources目录下,新增一个文件:example-changelog.xml,内容如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
    <changeSet author="[email protected]" id="example-1.0">

        <createTable tableName="EXAMPLE_COMPANY">
            <column name="ID" type="VARCHAR(36)">
                <constraints nullable="false"/>
            </column>
            <column name="NAME" type="VARCHAR(255)">
                <constraints nullable="false"/>
            </column>
            <column name="REALM_ID" type="VARCHAR(36)">
                <constraints nullable="false"/>
            </column>
        </createTable>

        <addPrimaryKey
                constraintName="PK_COMPANY"
                tableName="EXAMPLE_COMPANY"
                columnNames="ID"/>

    </changeSet>

</databaseChangeLog>

通知Keycloak这个JPA SPI的实现

在resources/META-INF/services目录下,新建一个文件:org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory,内容如下:

复制代码
com.dadaer.keycloak.jpa.CompanyJpaEntityProviderFactory

使用实体

在定义了Company这个实体以后,就可以对其进行增删改查等操作。因为实体本质上是JPA的Entity,所以需要使用JPA的API(EntityManager)来操作它。这里不对具体的调用做过多的详解,只把重要的代码分享出来供参考。

java 复制代码
private EntityManager getEntityManager() {
    return session.getProvider(JpaConnectionProvider.class).getEntityManager();
}

protected RealmModel getRealm() {
    return session.getContext().getRealm();
}
    
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public CompanyRepresentation addCompany(CompanyRepresentation companyRep) {

    Company entity = new Company();
    String id = companyRep.getId() == null ? KeycloakModelUtils.generateId() : companyRep.getId();
    entity.setId(id);
    entity.setName(companyRep.getName());
    entity.setRealmId(getRealm().getId());
    getEntityManager().persist(entity);

    companyRep.setId(id);
    return companyRep;
}

@GET
@Path("")
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<CompanyRepresentation> getCompanies() {

    List<Company> companyEntities = getEntityManager()
            .createNamedQuery("findByRealm", Company.class)
            .setParameter("realmId", getRealm().getId())
            .getResultList();

    List<CompanyRepresentation> result = new LinkedList<>();
    for (Company entity : companyEntities) {
        result.add(new CompanyRepresentation(entity));
    }

    return result;
}

这里需要重点看以下几点:

  • 通过KeycloakSession#getProvider方法获取到JPA的SPI示例,然后调用它上面的getEntityManager方法获取EntityManager对象
  • 通过EntityManager的API操作实体

结论

通过实现Keycloak的JPA的SPI,我们可以向Keycloak中增加实体对象,来满足业务需要。在实现的时候,需要注意的是,提供Liquibase的changelog文件。

相关推荐
yuren_xia3 小时前
RabbitMQ 知识详解(Java版)
java·rabbitmq·java-rabbitmq
kfyty7253 小时前
轻量级 ioc 框架 loveqq,支持接口上传 jar 格式的 starter 启动器并支持热加载其中的 bean
java·jvm·ioc·jar·热加载
早起鸟儿4 小时前
docker-Dockerfile 配置
java·linux·运维·docker
云边小网安4 小时前
java集合篇(六) ---- ListIterator 接口
java·开发语言·青少年编程·java集合
都叫我大帅哥4 小时前
Spring WebFlux:响应式编程的“未来战士”还是“花架子”?
java·spring·flux
都叫我大帅哥4 小时前
Reactor 深度解析:响应式编程的「核反应堆」是如何工作的?
java·spring
不太厉害的程序员4 小时前
NC65配置xml找不到Bean
xml·java·后端·eclipse
我在北国不背锅5 小时前
基于Java开发的浏览器自动化Playwright-MCP服务器
java·playwright·mcp
LUCIAZZZ5 小时前
钉钉机器人-自定义卡片推送快速入门
java·jvm·spring boot·机器人·钉钉·springboot
优秀1355 小时前
java33
java