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文件。

相关推荐
Dola_Pan25 分钟前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book28 分钟前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^1 小时前
Docker和K8S
java·docker·kubernetes
从心归零2 小时前
sshj使用代理连接服务器
java·服务器·sshj
IT毕设梦工厂3 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe4 小时前
分布式系统实战经验
java·分布式
是梦终空4 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss4 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸4 小时前
flink doris批量sink
java·前端·flink