How-to: Implement core services with JPA

本指南显示了如何使用 JPA 实现 Spring Authorization Servercore services。本指南的目的在于为您自己实现这些服务提供一个起点,以便对服务进行修改以满足您的需求。

Define the data model

本指南为数据模型提供了一个起点,并使用最简单的结构和数据类型。为了想出初始架构,我们首先查看核心服务使用的 domain objects

对于令牌、状态、元数据、设置和声明值除外,我们对所有列使用 JPA 默认列长度 255。实际上,您所用列的长度,甚至类型,可能需要定制。建议在部署到产品环境之前进行试验和测试。

Client Schema

RegisteredClient 域对象包含一些多值字段和一些需要存储任意键/值数据的设置字段。以下清单显示了 client 架构。

Client Schema
CREATE TABLE client (
    id varchar(255) NOT NULL,
    clientId varchar(255) NOT NULL,
    clientIdIssuedAt timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
    clientSecret varchar(255) DEFAULT NULL,
    clientSecretExpiresAt timestamp DEFAULT NULL,
    clientName varchar(255) NOT NULL,
    clientAuthenticationMethods varchar(1000) NOT NULL,
    authorizationGrantTypes varchar(1000) NOT NULL,
    redirectUris varchar(1000) DEFAULT NULL,
    postLogoutRedirectUris varchar(1000) DEFAULT NULL,
    scopes varchar(1000) NOT NULL,
    clientSettings varchar(2000) NOT NULL,
    tokenSettings varchar(2000) NOT NULL,
    PRIMARY KEY (id)
);

Authorization Schema

OAuth2Authorization 域对象更为复杂,包含多个多值字段以及许多任意长的令牌值、元数据、设置和声明值。内置 JDBC 实现利用扁平结构,它更注重性能而不是规范化,我们在此也采用这种方式。

很难找到在所有情况下和所有数据库供应商中都能正常工作的扁平化数据库架构。您可能需要对以下架构进行规范化或大幅度更改以满足您的需要。

以下列表显示 authorization 架构。

Authorization Schema
CREATE TABLE authorization (
    id varchar(255) NOT NULL,
    registeredClientId varchar(255) NOT NULL,
    principalName varchar(255) NOT NULL,
    authorizationGrantType varchar(255) NOT NULL,
    authorizedScopes varchar(1000) DEFAULT NULL,
    attributes varchar(4000) DEFAULT NULL,
    state varchar(500) DEFAULT NULL,
    authorizationCodeValue varchar(4000) DEFAULT NULL,
    authorizationCodeIssuedAt timestamp DEFAULT NULL,
    authorizationCodeExpiresAt timestamp DEFAULT NULL,
    authorizationCodeMetadata varchar(2000) DEFAULT NULL,
    accessTokenValue varchar(4000) DEFAULT NULL,
    accessTokenIssuedAt timestamp DEFAULT NULL,
    accessTokenExpiresAt timestamp DEFAULT NULL,
    accessTokenMetadata varchar(2000) DEFAULT NULL,
    accessTokenType varchar(255) DEFAULT NULL,
    accessTokenScopes varchar(1000) DEFAULT NULL,
    refreshTokenValue varchar(4000) DEFAULT NULL,
    refreshTokenIssuedAt timestamp DEFAULT NULL,
    refreshTokenExpiresAt timestamp DEFAULT NULL,
    refreshTokenMetadata varchar(2000) DEFAULT NULL,
    oidcIdTokenValue varchar(4000) DEFAULT NULL,
    oidcIdTokenIssuedAt timestamp DEFAULT NULL,
    oidcIdTokenExpiresAt timestamp DEFAULT NULL,
    oidcIdTokenMetadata varchar(2000) DEFAULT NULL,
    oidcIdTokenClaims varchar(2000) DEFAULT NULL,
    userCodeValue varchar(4000) DEFAULT NULL,
    userCodeIssuedAt timestamp DEFAULT NULL,
    userCodeExpiresAt timestamp DEFAULT NULL,
    userCodeMetadata varchar(2000) DEFAULT NULL,
    deviceCodeValue varchar(4000) DEFAULT NULL,
    deviceCodeIssuedAt timestamp DEFAULT NULL,
    deviceCodeExpiresAt timestamp DEFAULT NULL,
    deviceCodeMetadata varchar(2000) DEFAULT NULL,
    PRIMARY KEY (id)
);

OAuth2AuthorizationConsent 域对象是最简单的建模对象,除了复合键外,它仅包含一个多值字段。以下清单显示了 authorizationconsent 架构。

Authorization Consent Schema
CREATE TABLE authorizationConsent (
    registeredClientId varchar(255) NOT NULL,
    principalName varchar(255) NOT NULL,
    authorities varchar(1000) NOT NULL,
    PRIMARY KEY (registeredClientId, principalName)
);

Create JPA entities

前面的架构示例为我们创建的实体结构提供参考。

以下实体是最低限度标注,只是示例。它们允许动态创建架构,因此不需要手动执行上述 SQL 脚本。

Client Entity

以下清单显示了 Client 实体,该实体用于持久化从 RegisteredClient 域对象映射的信息。

Client Entity
link:{examples-dir}/main/java/sample/jpa/entity/client/Client.java[role=include]

Authorization Entity

以下清单显示了 Authorization 实体,该实体用于持久化从 OAuth2Authorization 域对象映射的信息。

Authorization Entity
link:{examples-dir}/main/java/sample/jpa/entity/authorization/Authorization.java[role=include]

以下清单显示了 AuthorizationConsent 实体,该实体用于持久化从 OAuth2AuthorizationConsent 域对象映射的信息。

Authorization Consent Entity
link:{examples-dir}/main/java/sample/jpa/entity/authorizationconsent/AuthorizationConsent.java[role=include]

Create Spring Data repositories

通过仔细检查每个核心服务的接口并查看 Jdbc 实现,我们可以推导出支持每个接口的 JPA 版本所需的一组最低限度的查询。

Client Repository

以下清单显示了 ClientRepository,它可以通过 id`和 `clientId`字段找到一个 `Client

Client Repository
link:{examples-dir}/main/java/sample/jpa/repository/client/ClientRepository.java[role=include]

Authorization Repository

以下示例中显示了 AuthorizationRepository,它可以通过 id 字段以及 stateauthorizationCodeValueaccessTokenValuerefreshTokenValueuserCodeValuedeviceCodeValue 令牌字段查找 Authorization。它还允许查询令牌字段的组合。

Authorization Repository
link:{examples-dir}/main/java/sample/jpa/repository/authorization/AuthorizationRepository.java[role=include]

以下清单显示了 AuthorizationConsentRepository,它可以通过形成复合主键的 registeredClientId`和 `principalName`字段找到并删除一个 `AuthorizationConsent

Authorization Consent Repository
link:{examples-dir}/main/java/sample/jpa/repository/authorizationconsent/AuthorizationConsentRepository.java[role=include]

Implement core services

有了上述 entitiesrepositories,我们便可以开始实现核心服务。通过查看 `Jdbc`实现,我们可以为枚举值转换到字符串值以及反之、为属性、设置、元数据和声明字段读写 JSON 数据派生出一组内部实用工具的最小合集。

请记住,将 JSON 数据写入具有固定长度的文本列已证明存在问题 Jdbc 实现。虽然这些示例继续进行此操作,但您可能需要将这些字段拆分为一个单独的表或支持任意长数据值的数据存储区。

Registered Client Repository

以下清单显示了 JpaRegisteredClientRepository,它使用 ClientRepository持久化 Client,并映射到 RegisteredClient域对象并从中映射出来。

RegisteredClientRepository Implementation
link:{examples-dir}/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java[role=include]

Authorization Service

以下清单显示了 JpaOAuth2AuthorizationService,它使用 AuthorizationRepository持久化 Authorization,并映射到 OAuth2Authorization域对象并从中映射出来。

OAuth2AuthorizationService Implementation
link:{examples-dir}/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java[role=include]

以下清单显示了 JpaOAuth2AuthorizationConsentService,它使用 AuthorizationConsentRepository持久化 AuthorizationConsent,并映射到 OAuth2AuthorizationConsent域对象并从中映射出来。

OAuth2AuthorizationConsentService Implementation
link:{examples-dir}/main/java/sample/jpa/service/authorizationconsent/JpaOAuth2AuthorizationConsentService.java[role=include]