Skip to content

Commit cba46dc

Browse files
committed
Add Redis implementations of core components to demo sample
1 parent 8edbc26 commit cba46dc

28 files changed

+2205
-70
lines changed

samples/README.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ The demo sample provides custom configuration for various features implemented b
1414
=== Run the Sample
1515

1616
* Run Authorization Server -> `./gradlew -b samples/demo-authorizationserver/samples-demo-authorizationserver.gradle bootRun`
17+
18+
NOTE: The default configuration registers the JDBC implementations of the core components: `RegisteredClientRepository`, `OAuth2AuthorizationService` and `OAuth2AuthorizationConsentService`.
19+
Alternatively, the custom Redis implementations are registered when the `redis` profile is activated, for example, when `--spring.profiles.active=redis` is passed into the command line.
20+
See the https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/[installation guide] for setting up Redis in your development environment and `application-redis.yml` for configuring the location of the Redis server host and port.
21+
1722
* Run Client -> `./gradlew -b samples/demo-client/samples-demo-client.gradle bootRun`
1823
* Run Resource Server -> `./gradlew -b samples/messages-resource/samples-messages-resource.gradle bootRun`
1924
* Go to `http://127.0.0.1:8080`

samples/demo-authorizationserver/samples-demo-authorizationserver.gradle

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ java {
1111
sourceCompatibility = JavaVersion.VERSION_17
1212
}
1313

14+
compileJava {
15+
options.compilerArgs << '-parameters'
16+
}
17+
1418
repositories {
1519
mavenCentral()
1620
maven { url "https://repo.spring.io/milestone" }
@@ -23,6 +27,10 @@ dependencies {
2327
implementation "org.springframework.boot:spring-boot-starter-security"
2428
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
2529
implementation "org.springframework.boot:spring-boot-starter-jdbc"
30+
implementation ("org.springframework.boot:spring-boot-starter-data-redis") {
31+
exclude group: "io.lettuce", module: "lettuce-core"
32+
}
33+
implementation "redis.clients:jedis"
2634
implementation project(":spring-security-oauth2-authorization-server")
2735
runtimeOnly "com.h2database:h2"
2836

samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

+6-70
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package sample.config;
1717

18-
import java.util.UUID;
19-
2018
import com.nimbusds.jose.jwk.JWKSet;
2119
import com.nimbusds.jose.jwk.RSAKey;
2220
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -28,6 +26,7 @@
2826

2927
import org.springframework.context.annotation.Bean;
3028
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.context.annotation.Profile;
3130
import org.springframework.core.Ordered;
3231
import org.springframework.core.annotation.Order;
3332
import org.springframework.http.MediaType;
@@ -37,20 +36,14 @@
3736
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
3837
import org.springframework.security.config.Customizer;
3938
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
40-
import org.springframework.security.oauth2.core.AuthorizationGrantType;
41-
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
42-
import org.springframework.security.oauth2.core.oidc.OidcScopes;
4339
import org.springframework.security.oauth2.jwt.JwtDecoder;
4440
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
4541
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
4642
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
47-
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
4843
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
4944
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
5045
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
5146
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
52-
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
53-
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
5447
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
5548
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
5649
import org.springframework.security.web.SecurityFilterChain;
@@ -131,81 +124,23 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(
131124

132125
// @formatter:off
133126
@Bean
127+
@Profile("!redis")
134128
public JdbcRegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
135-
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
136-
.clientId("messaging-client")
137-
.clientSecret("{noop}secret")
138-
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
139-
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
140-
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
141-
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
142-
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
143-
.redirectUri("http://127.0.0.1:8080/authorized")
144-
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
145-
.scope(OidcScopes.OPENID)
146-
.scope(OidcScopes.PROFILE)
147-
.scope("message.read")
148-
.scope("message.write")
149-
.scope("user.read")
150-
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
151-
.build();
152-
153-
RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
154-
.clientId("device-messaging-client")
155-
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
156-
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
157-
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
158-
.scope("message.read")
159-
.scope("message.write")
160-
.build();
161-
162-
RegisteredClient tokenExchangeClient = RegisteredClient.withId(UUID.randomUUID().toString())
163-
.clientId("token-client")
164-
.clientSecret("{noop}token")
165-
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
166-
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-exchange"))
167-
.scope("message.read")
168-
.scope("message.write")
169-
.build();
170-
171-
RegisteredClient mtlsDemoClient = RegisteredClient.withId(UUID.randomUUID().toString())
172-
.clientId("mtls-demo-client")
173-
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
174-
.clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
175-
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
176-
.scope("message.read")
177-
.scope("message.write")
178-
.clientSettings(
179-
ClientSettings.builder()
180-
.x509CertificateSubjectDN("CN=demo-client-sample,OU=Spring Samples,O=Spring,C=US")
181-
.jwkSetUrl("http://127.0.0.1:8080/jwks")
182-
.build()
183-
)
184-
.tokenSettings(
185-
TokenSettings.builder()
186-
.x509CertificateBoundAccessTokens(true)
187-
.build()
188-
)
189-
.build();
190-
191-
// Save registered client's in db as if in-memory
192129
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
193-
registeredClientRepository.save(messagingClient);
194-
registeredClientRepository.save(deviceClient);
195-
registeredClientRepository.save(tokenExchangeClient);
196-
registeredClientRepository.save(mtlsDemoClient);
197-
130+
RegisteredClients.defaults().forEach(registeredClientRepository::save);
198131
return registeredClientRepository;
199132
}
200133
// @formatter:on
201134

202135
@Bean
136+
@Profile("!redis")
203137
public JdbcOAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
204138
RegisteredClientRepository registeredClientRepository) {
205139
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
206140
}
207141

208142
@Bean
143+
@Profile("!redis")
209144
public JdbcOAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
210145
RegisteredClientRepository registeredClientRepository) {
211146
// Will be used by the ConsentController
@@ -235,6 +170,7 @@ public AuthorizationServerSettings authorizationServerSettings() {
235170
}
236171

237172
@Bean
173+
@Profile("!redis")
238174
public EmbeddedDatabase embeddedDatabase() {
239175
// @formatter:off
240176
return new EmbeddedDatabaseBuilder()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.config;
17+
18+
import java.util.Arrays;
19+
20+
import sample.data.redis.RedisOAuth2AuthorizationConsentService;
21+
import sample.data.redis.RedisOAuth2AuthorizationService;
22+
import sample.data.redis.RedisRegisteredClientRepository;
23+
import sample.data.redis.convert.BytesToClaimsHolderConverter;
24+
import sample.data.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
25+
import sample.data.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
26+
import sample.data.redis.convert.ClaimsHolderToBytesConverter;
27+
import sample.data.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
28+
import sample.data.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
29+
import sample.data.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
30+
import sample.data.redis.repository.OAuth2RegisteredClientRepository;
31+
import sample.data.redis.repository.OAuth2UserConsentRepository;
32+
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.context.annotation.Profile;
36+
import org.springframework.data.redis.connection.RedisConnectionFactory;
37+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
38+
import org.springframework.data.redis.core.RedisTemplate;
39+
import org.springframework.data.redis.core.convert.RedisCustomConversions;
40+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
41+
42+
/**
43+
* @author Joe Grandja
44+
* @since 1.4
45+
*/
46+
@Profile("redis")
47+
@Configuration(proxyBeanMethods = false)
48+
public class RedisConfig {
49+
50+
@Bean
51+
public RedisConnectionFactory redisConnectionFactory() {
52+
return new JedisConnectionFactory();
53+
}
54+
55+
@Bean
56+
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
57+
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
58+
redisTemplate.setConnectionFactory(redisConnectionFactory);
59+
return redisTemplate;
60+
}
61+
62+
@Bean
63+
public RedisCustomConversions redisCustomConversions() {
64+
return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
65+
new BytesToUsernamePasswordAuthenticationTokenConverter(),
66+
new OAuth2AuthorizationRequestToBytesConverter(), new BytesToOAuth2AuthorizationRequestConverter(),
67+
new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
68+
}
69+
70+
@Bean
71+
public RedisRegisteredClientRepository registeredClientRepository(
72+
OAuth2RegisteredClientRepository registeredClientRepository) {
73+
RedisRegisteredClientRepository redisRegisteredClientRepository = new RedisRegisteredClientRepository(registeredClientRepository);
74+
RegisteredClients.defaults().forEach(redisRegisteredClientRepository::save);
75+
return redisRegisteredClientRepository;
76+
}
77+
78+
@Bean
79+
public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
80+
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
81+
return new RedisOAuth2AuthorizationService(registeredClientRepository,
82+
authorizationGrantAuthorizationRepository);
83+
}
84+
85+
@Bean
86+
public RedisOAuth2AuthorizationConsentService authorizationConsentService(
87+
OAuth2UserConsentRepository userConsentRepository) {
88+
return new RedisOAuth2AuthorizationConsentService(userConsentRepository);
89+
}
90+
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.config;
17+
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.UUID;
21+
22+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
23+
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
24+
import org.springframework.security.oauth2.core.oidc.OidcScopes;
25+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
26+
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
27+
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
28+
29+
/**
30+
* @author Joe Grandja
31+
* @since 1.4
32+
*/
33+
final class RegisteredClients {
34+
35+
// @formatter:off
36+
static List<RegisteredClient> defaults() {
37+
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
38+
.clientId("messaging-client")
39+
.clientSecret("{noop}secret")
40+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
41+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
42+
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
43+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
44+
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
45+
.redirectUri("http://127.0.0.1:8080/authorized")
46+
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
47+
.scope(OidcScopes.OPENID)
48+
.scope(OidcScopes.PROFILE)
49+
.scope("message.read")
50+
.scope("message.write")
51+
.scope("user.read")
52+
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
53+
.build();
54+
55+
RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
56+
.clientId("device-messaging-client")
57+
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
58+
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
59+
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
60+
.scope("message.read")
61+
.scope("message.write")
62+
.build();
63+
64+
RegisteredClient tokenExchangeClient = RegisteredClient.withId(UUID.randomUUID().toString())
65+
.clientId("token-client")
66+
.clientSecret("{noop}token")
67+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
68+
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-exchange"))
69+
.scope("message.read")
70+
.scope("message.write")
71+
.build();
72+
73+
RegisteredClient mtlsDemoClient = RegisteredClient.withId(UUID.randomUUID().toString())
74+
.clientId("mtls-demo-client")
75+
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
76+
.clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
77+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
78+
.scope("message.read")
79+
.scope("message.write")
80+
.clientSettings(
81+
ClientSettings.builder()
82+
.x509CertificateSubjectDN("CN=demo-client-sample,OU=Spring Samples,O=Spring,C=US")
83+
.jwkSetUrl("http://127.0.0.1:8080/jwks")
84+
.build()
85+
)
86+
.tokenSettings(
87+
TokenSettings.builder()
88+
.x509CertificateBoundAccessTokens(true)
89+
.build()
90+
)
91+
.build();
92+
93+
return Arrays.asList(messagingClient, deviceClient, tokenExchangeClient, mtlsDemoClient);
94+
}
95+
// @formatter:on
96+
97+
}

0 commit comments

Comments
 (0)