Skip to content

Commit

Permalink
Add Redis implementations of core components to demo sample
Browse files Browse the repository at this point in the history
  • Loading branch information
jgrandja committed Sep 9, 2024
1 parent 5b7e815 commit e2ad4ac
Show file tree
Hide file tree
Showing 28 changed files with 2,205 additions and 70 deletions.
5 changes: 5 additions & 0 deletions samples/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ The demo sample provides custom configuration for various features implemented b
=== Run the Sample

* Run Authorization Server -> `./gradlew -b samples/demo-authorizationserver/samples-demo-authorizationserver.gradle bootRun`

NOTE: The default configuration registers the JDBC implementations of the core components: `RegisteredClientRepository`, `OAuth2AuthorizationService` and `OAuth2AuthorizationConsentService`.
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.
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.

* Run Client -> `./gradlew -b samples/demo-client/samples-demo-client.gradle bootRun`
* Run Resource Server -> `./gradlew -b samples/messages-resource/samples-messages-resource.gradle bootRun`
* Go to `http://127.0.0.1:8080`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ java {
sourceCompatibility = JavaVersion.VERSION_17
}

compileJava {
options.compilerArgs << '-parameters'
}

repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
Expand All @@ -23,6 +27,10 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-jdbc"
implementation ("org.springframework.boot:spring-boot-starter-data-redis") {
exclude group: "io.lettuce", module: "lettuce-core"
}
implementation "redis.clients:jedis"
implementation project(":spring-security-oauth2-authorization-server")
runtimeOnly "com.h2database:h2"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package sample.config;

import java.util.UUID;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
Expand All @@ -28,6 +26,7 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
Expand All @@ -37,20 +36,14 @@
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.web.SecurityFilterChain;
Expand Down Expand Up @@ -131,81 +124,23 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(

// @formatter:off
@Bean
@Profile("!redis")
public JdbcRegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.scope("user.read")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();

RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("device-messaging-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.scope("message.read")
.scope("message.write")
.build();

RegisteredClient tokenExchangeClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("token-client")
.clientSecret("{noop}token")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-exchange"))
.scope("message.read")
.scope("message.write")
.build();

RegisteredClient mtlsDemoClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("mtls-demo-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
.clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("message.read")
.scope("message.write")
.clientSettings(
ClientSettings.builder()
.x509CertificateSubjectDN("CN=demo-client-sample,OU=Spring Samples,O=Spring,C=US")
.jwkSetUrl("http://127.0.0.1:8080/jwks")
.build()
)
.tokenSettings(
TokenSettings.builder()
.x509CertificateBoundAccessTokens(true)
.build()
)
.build();

// Save registered client's in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(messagingClient);
registeredClientRepository.save(deviceClient);
registeredClientRepository.save(tokenExchangeClient);
registeredClientRepository.save(mtlsDemoClient);

RegisteredClients.defaults().forEach(registeredClientRepository::save);
return registeredClientRepository;
}
// @formatter:on

@Bean
@Profile("!redis")
public JdbcOAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}

@Bean
@Profile("!redis")
public JdbcOAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
// Will be used by the ConsentController
Expand Down Expand Up @@ -235,6 +170,7 @@ public AuthorizationServerSettings authorizationServerSettings() {
}

@Bean
@Profile("!redis")
public EmbeddedDatabase embeddedDatabase() {
// @formatter:off
return new EmbeddedDatabaseBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;

import java.util.Arrays;

import sample.data.redis.RedisOAuth2AuthorizationConsentService;
import sample.data.redis.RedisOAuth2AuthorizationService;
import sample.data.redis.RedisRegisteredClientRepository;
import sample.data.redis.convert.BytesToClaimsHolderConverter;
import sample.data.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
import sample.data.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
import sample.data.redis.convert.ClaimsHolderToBytesConverter;
import sample.data.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
import sample.data.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
import sample.data.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
import sample.data.redis.repository.OAuth2RegisteredClientRepository;
import sample.data.redis.repository.OAuth2UserConsentRepository;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.RedisCustomConversions;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;

/**
* @author Joe Grandja
* @since 1.4
*/
@Profile("redis")
@Configuration(proxyBeanMethods = false)
public class RedisConfig {

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory();
}

@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}

@Bean
public RedisCustomConversions redisCustomConversions() {
return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
new BytesToUsernamePasswordAuthenticationTokenConverter(),
new OAuth2AuthorizationRequestToBytesConverter(), new BytesToOAuth2AuthorizationRequestConverter(),
new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
}

@Bean
public RedisRegisteredClientRepository registeredClientRepository(
OAuth2RegisteredClientRepository registeredClientRepository) {
RedisRegisteredClientRepository redisRegisteredClientRepository = new RedisRegisteredClientRepository(registeredClientRepository);
RegisteredClients.defaults().forEach(redisRegisteredClientRepository::save);
return redisRegisteredClientRepository;
}

@Bean
public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
return new RedisOAuth2AuthorizationService(registeredClientRepository,
authorizationGrantAuthorizationRepository);
}

@Bean
public RedisOAuth2AuthorizationConsentService authorizationConsentService(
OAuth2UserConsentRepository userConsentRepository) {
return new RedisOAuth2AuthorizationConsentService(userConsentRepository);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.config;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;

/**
* @author Joe Grandja
* @since 1.4
*/
final class RegisteredClients {

// @formatter:off
static List<RegisteredClient> defaults() {
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.scope("user.read")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();

RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("device-messaging-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.scope("message.read")
.scope("message.write")
.build();

RegisteredClient tokenExchangeClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("token-client")
.clientSecret("{noop}token")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-exchange"))
.scope("message.read")
.scope("message.write")
.build();

RegisteredClient mtlsDemoClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("mtls-demo-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
.clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("message.read")
.scope("message.write")
.clientSettings(
ClientSettings.builder()
.x509CertificateSubjectDN("CN=demo-client-sample,OU=Spring Samples,O=Spring,C=US")
.jwkSetUrl("http://127.0.0.1:8080/jwks")
.build()
)
.tokenSettings(
TokenSettings.builder()
.x509CertificateBoundAccessTokens(true)
.build()
)
.build();

return Arrays.asList(messagingClient, deviceClient, tokenExchangeClient, mtlsDemoClient);
}
// @formatter:on

}
Loading

0 comments on commit e2ad4ac

Please sign in to comment.