Skip to content

Commit fffe227

Browse files
committed
Add How-to: Implement core services with Redis
Closes gh-1019
1 parent c942ed7 commit fffe227

26 files changed

+2237
-0
lines changed

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
** xref:guides/how-to-multitenancy.adoc[]
1212
** xref:guides/how-to-userinfo.adoc[]
1313
** xref:guides/how-to-jpa.adoc[]
14+
** xref:guides/how-to-redis.adoc[]
1415
** xref:guides/how-to-custom-claims-authorities.adoc[]
1516
** xref:guides/how-to-dynamic-client-registration.adoc[]
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
2+
[[how-to-redis]]
3+
= How-to: Implement core services with Redis
4+
:index-link: ../how-to.html
5+
:docs-dir: ..
6+
7+
This guide shows how to implement the xref:core-model-components.adoc[core services] of xref:index.adoc[Spring Authorization Server] with https://redis.io/[Redis].
8+
The purpose of this guide is to provide a starting point for implementing these services yourself, with the intention that you can make modifications to suit your needs.
9+
10+
* xref:guides/how-to-redis.adoc#define-entity-model[Define the entity model]
11+
* xref:guides/how-to-redis.adoc#create-spring-data-repositories[Create Spring Data repositories]
12+
* xref:guides/how-to-redis.adoc#implement-core-services[Implement core services]
13+
* xref:guides/how-to-redis.adoc#configure-core-services[Configure core services]
14+
15+
TIP: The code samples provided in this guide are located in the https://github.com/spring-projects/spring-authorization-server/tree/main/docs/src/main/java/sample[documentation samples] directory under the *_redis_* subdirectory.
16+
17+
[[define-entity-model]]
18+
== Define the entity model
19+
20+
The following defines the entity model representation for the `RegisteredClient`, `OAuth2Authorization` and `OAuth2AuthorizationConsent` domain classes.
21+
22+
* xref:guides/how-to-redis.adoc#registered-client-entity[Registered Client Entity]
23+
* xref:guides/how-to-redis.adoc#authorization-grant-entity[Authorization Grant _Base_ Entity]
24+
* xref:guides/how-to-redis.adoc#oauth2-authorization-code-grant-entity[Authorization Code Grant Entity (OAuth 2.0)]
25+
* xref:guides/how-to-redis.adoc#oidc-authorization-code-grant-entity[Authorization Code Grant Entity (OpenID Connect 1.0)]
26+
* xref:guides/how-to-redis.adoc#client-credentials-grant-entity[Client Credentials Grant Entity]
27+
* xref:guides/how-to-redis.adoc#device-code-grant-entity[Device Code Grant Entity]
28+
* xref:guides/how-to-redis.adoc#token-exchange-grant-entity[Token Exchange Grant Entity]
29+
* xref:guides/how-to-redis.adoc#authorization-consent-entity[Authorization Consent Entity]
30+
31+
[[registered-client-entity]]
32+
=== Registered Client Entity
33+
34+
The following listing shows the `OAuth2RegisteredClient` entity, which is used to persist information mapped from the xref:core-model-components.adoc#registered-client[`RegisteredClient`] domain class.
35+
36+
.OAuth2RegisteredClient Entity
37+
[source,java]
38+
----
39+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2RegisteredClient.java[]
40+
----
41+
42+
TIP: Click on the "Expand folded text" icon in the code sample above to display the full example.
43+
44+
[[authorization-grant-entity]]
45+
=== Authorization Grant _Base_ Entity
46+
47+
The entity model for the xref:core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain class is designed with a class hierarchy based on authorization grant type.
48+
49+
The following listing shows the `OAuth2AuthorizationGrantAuthorization` _base_ entity, which defines common attributes for each authorization grant type.
50+
51+
.OAuth2AuthorizationGrantAuthorization _Base_ Entity
52+
[source,java]
53+
----
54+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2AuthorizationGrantAuthorization.java[]
55+
----
56+
57+
[[oauth2-authorization-code-grant-entity]]
58+
=== Authorization Code Grant Entity (OAuth 2.0)
59+
60+
The following listing shows the `OAuth2AuthorizationCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, and defines additional attributes for the OAuth 2.0 `authorization_code` grant type.
61+
62+
.OAuth2AuthorizationCodeGrantAuthorization Entity
63+
[source,java]
64+
----
65+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java[]
66+
----
67+
68+
[[oidc-authorization-code-grant-entity]]
69+
=== Authorization Code Grant Entity (OpenID Connect 1.0)
70+
71+
The following listing shows the `OidcAuthorizationCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationCodeGrantAuthorization`, and defines additional attributes for the OpenID Connect 1.0 `authorization_code` grant type.
72+
73+
.OidcAuthorizationCodeGrantAuthorization Entity
74+
[source,java]
75+
----
76+
include::{examples-dir}/main/java/sample/redis/entity/OidcAuthorizationCodeGrantAuthorization.java[]
77+
----
78+
79+
[[client-credentials-grant-entity]]
80+
=== Client Credentials Grant Entity
81+
82+
The following listing shows the `OAuth2ClientCredentialsGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, for the `client_credentials` grant type.
83+
84+
.OAuth2ClientCredentialsGrantAuthorization Entity
85+
[source,java]
86+
----
87+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java[]
88+
----
89+
90+
[[device-code-grant-entity]]
91+
=== Device Code Grant Entity
92+
93+
The following listing shows the `OAuth2DeviceCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, and defines additional attributes for the `urn:ietf:params:oauth:grant-type:device_code` grant type.
94+
95+
.OAuth2DeviceCodeGrantAuthorization Entity
96+
[source,java]
97+
----
98+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2DeviceCodeGrantAuthorization.java[]
99+
----
100+
101+
[[token-exchange-grant-entity]]
102+
=== Token Exchange Grant Entity
103+
104+
The following listing shows the `OAuth2TokenExchangeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, for the `urn:ietf:params:oauth:grant-type:token-exchange` grant type.
105+
106+
.OAuth2TokenExchangeGrantAuthorization Entity
107+
[source,java]
108+
----
109+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2TokenExchangeGrantAuthorization.java[]
110+
----
111+
112+
[[authorization-consent-entity]]
113+
=== Authorization Consent Entity
114+
115+
The following listing shows the `OAuth2UserConsent` entity, which is used to persist information mapped from the xref:core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain class.
116+
117+
.OAuth2UserConsent Entity
118+
[source,java]
119+
----
120+
include::{examples-dir}/main/java/sample/redis/entity/OAuth2UserConsent.java[]
121+
----
122+
123+
[[create-spring-data-repositories]]
124+
== Create Spring Data repositories
125+
126+
By closely examining the interfaces of each core service and reviewing the `Jdbc` implementations, we can derive a minimal set of queries needed for supporting a Redis version of each interface.
127+
128+
* xref:guides/how-to-redis.adoc#registered-client-repository[Registered Client Repository]
129+
* xref:guides/how-to-redis.adoc#authorization-grant-repository[Authorization Grant Repository]
130+
* xref:guides/how-to-redis.adoc#authorization-consent-repository[Authorization Consent Repository]
131+
132+
[[registered-client-repository]]
133+
=== Registered Client Repository
134+
135+
The following listing shows the `OAuth2RegisteredClientRepository`, which is able to find a xref:guides/how-to-redis.adoc#registered-client-entity[`OAuth2RegisteredClient`] by the `id` and `clientId` fields.
136+
137+
.OAuth2RegisteredClientRepository
138+
[source,java]
139+
----
140+
include::{examples-dir}/main/java/sample/redis/repository/OAuth2RegisteredClientRepository.java[]
141+
----
142+
143+
[[authorization-grant-repository]]
144+
=== Authorization Grant Repository
145+
146+
The following listing shows the `OAuth2AuthorizationGrantAuthorizationRepository`, which is able to find an xref:guides/how-to-redis.adoc#authorization-grant-entity[`OAuth2AuthorizationGrantAuthorization`] by the `id` field as well as by `state`, `authorizationCode`, `accessToken`, `refreshToken`, `idToken`, `deviceState`, `userCode` and `deviceCode` values.
147+
148+
.OAuth2AuthorizationGrantAuthorizationRepository
149+
[source,java]
150+
----
151+
include::{examples-dir}/main/java/sample/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java[]
152+
----
153+
154+
[[authorization-consent-repository]]
155+
=== Authorization Consent Repository
156+
157+
The following listing shows the `OAuth2UserConsentRepository`, which is able to find and delete an xref:guides/how-to-redis.adoc#authorization-consent-entity[`OAuth2UserConsent`] by the `registeredClientId` and `principalName` fields that form the composite primary key.
158+
159+
.OAuth2UserConsentRepository
160+
[source,java]
161+
----
162+
include::{examples-dir}/main/java/sample/redis/repository/OAuth2UserConsentRepository.java[]
163+
----
164+
165+
[[implement-core-services]]
166+
== Implement core services
167+
168+
With the above xref:guides/how-to-redis.adoc#define-entity-model[entities] and xref:guides/how-to-redis.adoc#create-spring-data-repositories[repositories], we can begin implementing the core services.
169+
170+
TIP: The core services make use of the `ModelMapper` utility class for converting to and from the domain object (e.g. `RegisteredClient`) to the entity model representation (e.g. `OAuth2RegisteredClient`).
171+
172+
* xref:guides/how-to-redis.adoc#redis-registered-client-repository[Registered Client Repository]
173+
* xref:guides/how-to-redis.adoc#redis-authorization-service[Authorization Service]
174+
* xref:guides/how-to-redis.adoc#redis-authorization-consent-service[Authorization Consent Service]
175+
176+
[[redis-registered-client-repository]]
177+
=== Registered Client Repository
178+
179+
The following listing shows the `RedisRegisteredClientRepository`, which uses an xref:guides/how-to-redis.adoc#registered-client-repository[`OAuth2RegisteredClientRepository`] for persisting an xref:guides/how-to-redis.adoc#registered-client-entity[`OAuth2RegisteredClient`] and maps to and from the xref:core-model-components.adoc#registered-client[`RegisteredClient`] domain object, using the `ModelMapper` utility class.
180+
181+
.RedisRegisteredClientRepository
182+
[source,java]
183+
----
184+
include::{examples-dir}/main/java/sample/redis/service/RedisRegisteredClientRepository.java[]
185+
----
186+
187+
[[redis-authorization-service]]
188+
=== Authorization Service
189+
190+
The following listing shows the `RedisOAuth2AuthorizationService`, which uses an xref:guides/how-to-redis.adoc#authorization-grant-repository[`OAuth2AuthorizationGrantAuthorizationRepository`] for persisting an xref:guides/how-to-redis.adoc#authorization-grant-entity[`OAuth2AuthorizationGrantAuthorization`] and maps to and from the xref:core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object, using the `ModelMapper` utility class.
191+
192+
.RedisOAuth2AuthorizationService
193+
[source,java]
194+
----
195+
include::{examples-dir}/main/java/sample/redis/service/RedisOAuth2AuthorizationService.java[]
196+
----
197+
198+
[[redis-authorization-consent-service]]
199+
=== Authorization Consent Service
200+
201+
The following listing shows the `RedisOAuth2AuthorizationConsentService`, which uses an xref:guides/how-to-redis.adoc#authorization-consent-repository[`OAuth2UserConsentRepository`] for persisting an xref:guides/how-to-redis.adoc#authorization-consent-entity[`OAuth2UserConsent`] and maps to and from the xref:core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object, using the `ModelMapper` utility class.
202+
203+
.RedisOAuth2AuthorizationConsentService
204+
[source,java]
205+
----
206+
include::{examples-dir}/main/java/sample/redis/service/RedisOAuth2AuthorizationConsentService.java[]
207+
----
208+
209+
[[configure-core-services]]
210+
== Configure core services
211+
212+
The following example shows how to configure the core services:
213+
214+
.RedisConfig
215+
[source,java]
216+
----
217+
include::{examples-dir}/main/java/sample/redis/config/RedisConfig.java[]
218+
----
219+
220+
<1> Activate the Spring Data Redis repositories under the `sample.redis.repository` base package.
221+
<2> Use the https://docs.spring.io/spring-data/redis/reference/redis/drivers.html#redis:connectors:jedis[Jedis] Connector.
222+
<3> Register the custom ``Converter``'s that perform the Object-to-Hash conversion before persisting to Redis.
223+
<4> Register the `RedisRegisteredClientRepository` with the activated `OAuth2RegisteredClientRepository`.
224+
<5> Register the `RedisOAuth2AuthorizationService` with the activated `OAuth2AuthorizationGrantAuthorizationRepository`.
225+
<6> Register the `RedisOAuth2AuthorizationConsentService` with the activated `OAuth2UserConsentRepository`.

docs/spring-authorization-server-docs.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ java {
1212
sourceCompatibility = JavaVersion.VERSION_17
1313
}
1414

15+
compileJava {
16+
options.compilerArgs << '-parameters'
17+
}
18+
1519
antora {
1620
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
1721
environment = [
@@ -57,6 +61,10 @@ dependencies {
5761
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
5862
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
5963
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
64+
implementation ("org.springframework.boot:spring-boot-starter-data-redis") {
65+
exclude group: "io.lettuce", module: "lettuce-core"
66+
}
67+
implementation "redis.clients:jedis"
6068
implementation "org.springframework:spring-webflux"
6169
implementation project(":spring-security-oauth2-authorization-server")
6270
runtimeOnly "com.h2database:h2"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2020-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package sample.redis.config;
17+
18+
import java.util.Arrays;
19+
20+
import sample.redis.convert.BytesToClaimsHolderConverter;
21+
import sample.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
22+
import sample.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
23+
import sample.redis.convert.ClaimsHolderToBytesConverter;
24+
import sample.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
25+
import sample.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
26+
import sample.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
27+
import sample.redis.repository.OAuth2RegisteredClientRepository;
28+
import sample.redis.repository.OAuth2UserConsentRepository;
29+
import sample.redis.service.RedisOAuth2AuthorizationConsentService;
30+
import sample.redis.service.RedisOAuth2AuthorizationService;
31+
import sample.redis.service.RedisRegisteredClientRepository;
32+
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.data.redis.connection.RedisConnectionFactory;
36+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
37+
import org.springframework.data.redis.core.RedisTemplate;
38+
import org.springframework.data.redis.core.convert.RedisCustomConversions;
39+
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
40+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
41+
42+
@EnableRedisRepositories("sample.redis.repository") // <1>
43+
@Configuration(proxyBeanMethods = false)
44+
public class RedisConfig {
45+
46+
@Bean
47+
public RedisConnectionFactory redisConnectionFactory() {
48+
return new JedisConnectionFactory(); // <2>
49+
}
50+
51+
@Bean
52+
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
53+
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
54+
redisTemplate.setConnectionFactory(redisConnectionFactory);
55+
return redisTemplate;
56+
}
57+
58+
@Bean
59+
public RedisCustomConversions redisCustomConversions() { // <3>
60+
return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
61+
new BytesToUsernamePasswordAuthenticationTokenConverter(),
62+
new OAuth2AuthorizationRequestToBytesConverter(), new BytesToOAuth2AuthorizationRequestConverter(),
63+
new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
64+
}
65+
66+
@Bean
67+
public RedisRegisteredClientRepository registeredClientRepository(
68+
OAuth2RegisteredClientRepository registeredClientRepository) {
69+
return new RedisRegisteredClientRepository(registeredClientRepository); // <4>
70+
}
71+
72+
@Bean
73+
public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
74+
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
75+
return new RedisOAuth2AuthorizationService(registeredClientRepository,
76+
authorizationGrantAuthorizationRepository); // <5>
77+
}
78+
79+
@Bean
80+
public RedisOAuth2AuthorizationConsentService authorizationConsentService(
81+
OAuth2UserConsentRepository userConsentRepository) {
82+
return new RedisOAuth2AuthorizationConsentService(userConsentRepository); // <6>
83+
}
84+
85+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2020-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package sample.redis.convert;
17+
18+
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
20+
21+
import org.springframework.core.convert.converter.Converter;
22+
import org.springframework.data.convert.ReadingConverter;
23+
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
24+
import org.springframework.security.jackson2.SecurityJackson2Modules;
25+
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
26+
27+
@ReadingConverter
28+
public class BytesToClaimsHolderConverter
29+
implements Converter<byte[], OAuth2AuthorizationGrantAuthorization.ClaimsHolder> {
30+
31+
private final Jackson2JsonRedisSerializer<OAuth2AuthorizationGrantAuthorization.ClaimsHolder> serializer;
32+
33+
public BytesToClaimsHolderConverter() {
34+
ObjectMapper objectMapper = new ObjectMapper();
35+
objectMapper
36+
.registerModules(SecurityJackson2Modules.getModules(BytesToClaimsHolderConverter.class.getClassLoader()));
37+
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
38+
objectMapper.addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class);
39+
this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper,
40+
OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class);
41+
}
42+
43+
@Override
44+
public OAuth2AuthorizationGrantAuthorization.ClaimsHolder convert(byte[] value) {
45+
return this.serializer.deserialize(value);
46+
}
47+
48+
}

0 commit comments

Comments
 (0)