How-to: Implement core services with JPA
本指南显示了如何使用 JPA 实现 Spring Authorization Server 的 core services。本指南的目的在于为您自己实现这些服务提供一个起点,以便对服务进行修改以满足您的需求。
Define the data model
本指南为数据模型提供了一个起点,并使用最简单的结构和数据类型。为了想出初始架构,我们首先查看核心服务使用的 domain objects。
对于令牌、状态、元数据、设置和声明值除外,我们对所有列使用 JPA 默认列长度 255。实际上,您所用列的长度,甚至类型,可能需要定制。建议在部署到产品环境之前进行试验和测试。 |
Client Schema
RegisteredClient
域对象包含一些多值字段和一些需要存储任意键/值数据的设置字段。以下清单显示了 client
架构。
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
架构。
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)
);
Authorization Consent Schema
OAuth2AuthorizationConsent
域对象是最简单的建模对象,除了复合键外,它仅包含一个多值字段。以下清单显示了 authorizationconsent
架构。
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
域对象映射的信息。
link:{examples-dir}/main/java/sample/jpa/entity/client/Client.java[role=include]
Authorization Entity
以下清单显示了 Authorization
实体,该实体用于持久化从 OAuth2Authorization
域对象映射的信息。
link:{examples-dir}/main/java/sample/jpa/entity/authorization/Authorization.java[role=include]
Authorization Consent Entity
以下清单显示了 AuthorizationConsent
实体,该实体用于持久化从 OAuth2AuthorizationConsent
域对象映射的信息。
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
。
link:{examples-dir}/main/java/sample/jpa/repository/client/ClientRepository.java[role=include]
Authorization Repository
以下示例中显示了 AuthorizationRepository
,它可以通过 id
字段以及 state
、authorizationCodeValue
、accessTokenValue
、refreshTokenValue
、userCodeValue
和 deviceCodeValue
令牌字段查找 Authorization
。它还允许查询令牌字段的组合。
link:{examples-dir}/main/java/sample/jpa/repository/authorization/AuthorizationRepository.java[role=include]
Authorization Consent Repository
以下清单显示了 AuthorizationConsentRepository
,它可以通过形成复合主键的 registeredClientId`和 `principalName`字段找到并删除一个 `AuthorizationConsent
。
link:{examples-dir}/main/java/sample/jpa/repository/authorizationconsent/AuthorizationConsentRepository.java[role=include]
Implement core services
有了上述 entities和 repositories,我们便可以开始实现核心服务。通过查看 `Jdbc`实现,我们可以为枚举值转换到字符串值以及反之、为属性、设置、元数据和声明字段读写 JSON 数据派生出一组内部实用工具的最小合集。
请记住,将 JSON 数据写入具有固定长度的文本列已证明存在问题 Jdbc
实现。虽然这些示例继续进行此操作,但您可能需要将这些字段拆分为一个单独的表或支持任意长数据值的数据存储区。
Registered Client Repository
以下清单显示了 JpaRegisteredClientRepository
,它使用 ClientRepository
持久化 Client
,并映射到 RegisteredClient
域对象并从中映射出来。
RegisteredClientRepository
Implementationlink:{examples-dir}/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java[role=include]
Authorization Service
以下清单显示了 JpaOAuth2AuthorizationService
,它使用 AuthorizationRepository
持久化 Authorization
,并映射到 OAuth2Authorization
域对象并从中映射出来。
OAuth2AuthorizationService
Implementationlink:{examples-dir}/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java[role=include]
Authorization Consent Service
以下清单显示了 JpaOAuth2AuthorizationConsentService
,它使用 AuthorizationConsentRepository
持久化 AuthorizationConsent
,并映射到 OAuth2AuthorizationConsent
域对象并从中映射出来。
OAuth2AuthorizationConsentService
Implementationlink:{examples-dir}/main/java/sample/jpa/service/authorizationconsent/JpaOAuth2AuthorizationConsentService.java[role=include]