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="erik.mulder@docdatapayments.com" 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文件。

相关推荐
老鼠只爱大米22 分钟前
Java 设计模式之适配器模式:系统集成的万能接口
java·设计模式·适配器模式·adapter·java设计模式
一叶飘零_sweeeet23 分钟前
Java+EasyExcel 打造学习平台视频学习时长统计系统
java·报表·easyexcel
Go away, devil24 分钟前
Java-----集合
java·开发语言
JIngJaneIL26 分钟前
旅游|内蒙古景点旅游|基于Springboot+Vue的内蒙古景点旅游管理系统设计与实现(源码+数据库+文档)
java·vue.js·spring boot·论文·旅游·毕设·内蒙古景点旅游
新之助小锅1 小时前
java版连接汇川PLC,发送数据,读取数据,保持重新链接,适用安卓
android·java·python
无糖冰可乐213 小时前
IDEA多java版本切换
java·ide·intellij-idea
合作小小程序员小小店3 小时前
web开发,在线%超市销售%管理系统,基于idea,html,jsp,java,ssh,sql server数据库。
java·前端·sqlserver·ssh·intellij-idea
brucelee1863 小时前
IntelliJ IDEA 设置 Local History 永久保留
java·ide·intellij-idea
Pluto_CSND5 小时前
Java中的静态代理与动态代理(Proxy.newProxyInstance)
java·开发语言
百***46456 小时前
Java进阶-在Ubuntu上部署SpringBoot应用
java·spring boot·ubuntu