From e2ad4ac361b24eceaac0f993add0c1114614b11e Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:22:29 -0400 Subject: [PATCH] Add Redis implementations of core components to demo sample --- samples/README.adoc | 5 + .../samples-demo-authorizationserver.gradle | 8 + .../config/AuthorizationServerConfig.java | 76 +-- .../main/java/sample/config/RedisConfig.java | 91 ++++ .../java/sample/config/RegisteredClients.java | 97 ++++ .../java/sample/data/redis/ModelMapper.java | 502 ++++++++++++++++++ ...edisOAuth2AuthorizationConsentService.java | 62 +++ .../RedisOAuth2AuthorizationService.java | 127 +++++ .../RedisRegisteredClientRepository.java | 62 +++ .../convert/BytesToClaimsHolderConverter.java | 52 ++ ...ToOAuth2AuthorizationRequestConverter.java | 49 ++ ...ePasswordAuthenticationTokenConverter.java | 48 ++ .../data/redis/convert/ClaimsHolderMixin.java | 40 ++ .../convert/ClaimsHolderToBytesConverter.java | 52 ++ ...2AuthorizationRequestToBytesConverter.java | 49 ++ ...rdAuthenticationTokenToBytesConverter.java | 48 ++ ...h2AuthorizationCodeGrantAuthorization.java | 75 +++ ...OAuth2AuthorizationGrantAuthorization.java | 178 +++++++ ...h2ClientCredentialsGrantAuthorization.java | 31 ++ .../OAuth2DeviceCodeGrantAuthorization.java | 89 ++++ .../redis/model/OAuth2RegisteredClient.java | 248 +++++++++ ...OAuth2TokenExchangeGrantAuthorization.java | 31 ++ .../data/redis/model/OAuth2UserConsent.java | 67 +++ ...dcAuthorizationCodeGrantAuthorization.java | 61 +++ ...orizationGrantAuthorizationRepository.java | 53 ++ .../OAuth2RegisteredClientRepository.java | 32 ++ .../OAuth2UserConsentRepository.java | 33 ++ .../src/main/resources/application-redis.yml | 9 + 28 files changed, 2205 insertions(+), 70 deletions(-) create mode 100644 samples/demo-authorizationserver/src/main/java/sample/config/RedisConfig.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/config/RegisteredClients.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/ModelMapper.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationConsentService.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationService.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisRegisteredClientRepository.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToClaimsHolderConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderMixin.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderToBytesConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationCodeGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2ClientCredentialsGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2DeviceCodeGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2RegisteredClient.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2TokenExchangeGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2UserConsent.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OidcAuthorizationCodeGrantAuthorization.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2RegisteredClientRepository.java create mode 100644 samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2UserConsentRepository.java create mode 100644 samples/demo-authorizationserver/src/main/resources/application-redis.yml diff --git a/samples/README.adoc b/samples/README.adoc index 3378a6232..a167e7d06 100644 --- a/samples/README.adoc +++ b/samples/README.adoc @@ -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` diff --git a/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle b/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle index f6cfe92ba..d678597a5 100644 --- a/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle +++ b/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle @@ -11,6 +11,10 @@ java { sourceCompatibility = JavaVersion.VERSION_17 } +compileJava { + options.compilerArgs << '-parameters' +} + repositories { mavenCentral() maven { url "https://repo.spring.io/milestone" } @@ -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" diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index 5ed3a5617..2f42665bb 100644 --- a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -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; @@ -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; @@ -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; @@ -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 @@ -235,6 +170,7 @@ public AuthorizationServerSettings authorizationServerSettings() { } @Bean + @Profile("!redis") public EmbeddedDatabase embeddedDatabase() { // @formatter:off return new EmbeddedDatabaseBuilder() diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/RedisConfig.java b/samples/demo-authorizationserver/src/main/java/sample/config/RedisConfig.java new file mode 100644 index 000000000..ab8fd5ff8 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/config/RedisConfig.java @@ -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 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); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/RegisteredClients.java b/samples/demo-authorizationserver/src/main/java/sample/config/RegisteredClients.java new file mode 100644 index 000000000..733c8aa6c --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/config/RegisteredClients.java @@ -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 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 + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/ModelMapper.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/ModelMapper.java new file mode 100644 index 000000000..72fe43e53 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/ModelMapper.java @@ -0,0 +1,502 @@ +/* + * 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.data.redis; + +import java.security.Principal; + +import sample.data.redis.model.OAuth2AuthorizationCodeGrantAuthorization; +import sample.data.redis.model.OAuth2AuthorizationGrantAuthorization; +import sample.data.redis.model.OAuth2ClientCredentialsGrantAuthorization; +import sample.data.redis.model.OAuth2DeviceCodeGrantAuthorization; +import sample.data.redis.model.OAuth2RegisteredClient; +import sample.data.redis.model.OAuth2TokenExchangeGrantAuthorization; +import sample.data.redis.model.OAuth2UserConsent; +import sample.data.redis.model.OidcAuthorizationCodeGrantAuthorization; + +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2DeviceCode; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.OAuth2UserCode; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +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.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * @author Joe Grandja + * @since 1.4 + */ +final class ModelMapper { + + static OAuth2RegisteredClient convertOAuth2RegisteredClient(RegisteredClient registeredClient) { + OAuth2RegisteredClient.ClientSettings clientSettings = new OAuth2RegisteredClient.ClientSettings( + registeredClient.getClientSettings().isRequireProofKey(), + registeredClient.getClientSettings().isRequireAuthorizationConsent(), + registeredClient.getClientSettings().getJwkSetUrl(), + registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm(), + registeredClient.getClientSettings().getX509CertificateSubjectDN()); + + OAuth2RegisteredClient.TokenSettings tokenSettings = new OAuth2RegisteredClient.TokenSettings( + registeredClient.getTokenSettings().getAuthorizationCodeTimeToLive(), + registeredClient.getTokenSettings().getAccessTokenTimeToLive(), + registeredClient.getTokenSettings().getAccessTokenFormat(), + registeredClient.getTokenSettings().getDeviceCodeTimeToLive(), + registeredClient.getTokenSettings().isReuseRefreshTokens(), + registeredClient.getTokenSettings().getRefreshTokenTimeToLive(), + registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm(), + registeredClient.getTokenSettings().isX509CertificateBoundAccessTokens()); + + return new OAuth2RegisteredClient(registeredClient.getId(), registeredClient.getClientId(), + registeredClient.getClientIdIssuedAt(), registeredClient.getClientSecret(), + registeredClient.getClientSecretExpiresAt(), registeredClient.getClientName(), + registeredClient.getClientAuthenticationMethods(), registeredClient.getAuthorizationGrantTypes(), + registeredClient.getRedirectUris(), registeredClient.getPostLogoutRedirectUris(), + registeredClient.getScopes(), clientSettings, tokenSettings); + } + + static OAuth2UserConsent convertOAuth2UserConsent(OAuth2AuthorizationConsent authorizationConsent) { + String id = authorizationConsent.getRegisteredClientId() + .concat("-") + .concat(authorizationConsent.getPrincipalName()); + return new OAuth2UserConsent(id, authorizationConsent.getRegisteredClientId(), + authorizationConsent.getPrincipalName(), authorizationConsent.getAuthorities()); + } + + static OAuth2AuthorizationGrantAuthorization convertOAuth2AuthorizationGrantAuthorization( + OAuth2Authorization authorization) { + + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorization.getAuthorizationGrantType())) { + OAuth2AuthorizationRequest authorizationRequest = authorization + .getAttribute(OAuth2AuthorizationRequest.class.getName()); + return authorizationRequest.getScopes().contains(OidcScopes.OPENID) + ? convertOidcAuthorizationCodeGrantAuthorization(authorization) + : convertOAuth2AuthorizationCodeGrantAuthorization(authorization); + } + else if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(authorization.getAuthorizationGrantType())) { + return convertOAuth2ClientCredentialsGrantAuthorization(authorization); + } + else if (AuthorizationGrantType.DEVICE_CODE.equals(authorization.getAuthorizationGrantType())) { + return convertOAuth2DeviceCodeGrantAuthorization(authorization); + } + else if (AuthorizationGrantType.TOKEN_EXCHANGE.equals(authorization.getAuthorizationGrantType())) { + return convertOAuth2TokenExchangeGrantAuthorization(authorization); + } + return null; + } + + static OidcAuthorizationCodeGrantAuthorization convertOidcAuthorizationCodeGrantAuthorization( + OAuth2Authorization authorization) { + OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode authorizationCode = extractAuthorizationCode( + authorization); + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = extractAccessToken(authorization); + OAuth2AuthorizationGrantAuthorization.RefreshToken refreshToken = extractRefreshToken(authorization); + OidcAuthorizationCodeGrantAuthorization.IdToken idToken = extractIdToken(authorization); + + return new OidcAuthorizationCodeGrantAuthorization(authorization.getId(), authorization.getRegisteredClientId(), + authorization.getPrincipalName(), authorization.getAuthorizedScopes(), accessToken, refreshToken, + authorization.getAttribute(Principal.class.getName()), + authorization.getAttribute(OAuth2AuthorizationRequest.class.getName()), authorizationCode, + authorization.getAttribute(OAuth2ParameterNames.STATE), idToken); + } + + static OAuth2AuthorizationCodeGrantAuthorization convertOAuth2AuthorizationCodeGrantAuthorization( + OAuth2Authorization authorization) { + + OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode authorizationCode = extractAuthorizationCode( + authorization); + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = extractAccessToken(authorization); + OAuth2AuthorizationGrantAuthorization.RefreshToken refreshToken = extractRefreshToken(authorization); + + return new OAuth2AuthorizationCodeGrantAuthorization(authorization.getId(), + authorization.getRegisteredClientId(), authorization.getPrincipalName(), + authorization.getAuthorizedScopes(), accessToken, refreshToken, + authorization.getAttribute(Principal.class.getName()), + authorization.getAttribute(OAuth2AuthorizationRequest.class.getName()), authorizationCode, + authorization.getAttribute(OAuth2ParameterNames.STATE)); + } + + static OAuth2ClientCredentialsGrantAuthorization convertOAuth2ClientCredentialsGrantAuthorization( + OAuth2Authorization authorization) { + + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = extractAccessToken(authorization); + + return new OAuth2ClientCredentialsGrantAuthorization(authorization.getId(), + authorization.getRegisteredClientId(), authorization.getPrincipalName(), + authorization.getAuthorizedScopes(), accessToken); + } + + static OAuth2DeviceCodeGrantAuthorization convertOAuth2DeviceCodeGrantAuthorization( + OAuth2Authorization authorization) { + + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = extractAccessToken(authorization); + OAuth2AuthorizationGrantAuthorization.RefreshToken refreshToken = extractRefreshToken(authorization); + OAuth2DeviceCodeGrantAuthorization.DeviceCode deviceCode = extractDeviceCode(authorization); + OAuth2DeviceCodeGrantAuthorization.UserCode userCode = extractUserCode(authorization); + + return new OAuth2DeviceCodeGrantAuthorization(authorization.getId(), authorization.getRegisteredClientId(), + authorization.getPrincipalName(), authorization.getAuthorizedScopes(), accessToken, refreshToken, + authorization.getAttribute(Principal.class.getName()), deviceCode, userCode, + authorization.getAttribute(OAuth2ParameterNames.SCOPE), + authorization.getAttribute(OAuth2ParameterNames.STATE)); + } + + static OAuth2TokenExchangeGrantAuthorization convertOAuth2TokenExchangeGrantAuthorization( + OAuth2Authorization authorization) { + + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = extractAccessToken(authorization); + + return new OAuth2TokenExchangeGrantAuthorization(authorization.getId(), authorization.getRegisteredClientId(), + authorization.getPrincipalName(), authorization.getAuthorizedScopes(), accessToken); + } + + static OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode extractAuthorizationCode( + OAuth2Authorization authorization) { + OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode authorizationCode = null; + if (authorization.getToken(OAuth2AuthorizationCode.class) != null) { + OAuth2Authorization.Token oauth2AuthorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + authorizationCode = new OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode( + oauth2AuthorizationCode.getToken().getTokenValue(), + oauth2AuthorizationCode.getToken().getIssuedAt(), oauth2AuthorizationCode.getToken().getExpiresAt(), + oauth2AuthorizationCode.isInvalidated()); + } + return authorizationCode; + } + + static OAuth2AuthorizationGrantAuthorization.AccessToken extractAccessToken(OAuth2Authorization authorization) { + OAuth2AuthorizationGrantAuthorization.AccessToken accessToken = null; + if (authorization.getAccessToken() != null) { + OAuth2Authorization.Token oauth2AccessToken = authorization.getAccessToken(); + OAuth2TokenFormat tokenFormat = null; + if (OAuth2TokenFormat.SELF_CONTAINED.getValue() + .equals(oauth2AccessToken.getMetadata(OAuth2TokenFormat.class.getName()))) { + tokenFormat = OAuth2TokenFormat.SELF_CONTAINED; + } + else if (OAuth2TokenFormat.REFERENCE.getValue() + .equals(oauth2AccessToken.getMetadata(OAuth2TokenFormat.class.getName()))) { + tokenFormat = OAuth2TokenFormat.REFERENCE; + } + accessToken = new OAuth2AuthorizationGrantAuthorization.AccessToken( + oauth2AccessToken.getToken().getTokenValue(), oauth2AccessToken.getToken().getIssuedAt(), + oauth2AccessToken.getToken().getExpiresAt(), oauth2AccessToken.isInvalidated(), + oauth2AccessToken.getToken().getTokenType(), oauth2AccessToken.getToken().getScopes(), tokenFormat, + new OAuth2AuthorizationGrantAuthorization.ClaimsHolder(oauth2AccessToken.getClaims())); + } + return accessToken; + } + + static OAuth2AuthorizationGrantAuthorization.RefreshToken extractRefreshToken(OAuth2Authorization authorization) { + OAuth2AuthorizationGrantAuthorization.RefreshToken refreshToken = null; + if (authorization.getRefreshToken() != null) { + OAuth2Authorization.Token oauth2RefreshToken = authorization.getRefreshToken(); + refreshToken = new OAuth2AuthorizationGrantAuthorization.RefreshToken( + oauth2RefreshToken.getToken().getTokenValue(), oauth2RefreshToken.getToken().getIssuedAt(), + oauth2RefreshToken.getToken().getExpiresAt(), oauth2RefreshToken.isInvalidated()); + } + return refreshToken; + } + + static OidcAuthorizationCodeGrantAuthorization.IdToken extractIdToken(OAuth2Authorization authorization) { + OidcAuthorizationCodeGrantAuthorization.IdToken idToken = null; + if (authorization.getToken(OidcIdToken.class) != null) { + OAuth2Authorization.Token oidcIdToken = authorization.getToken(OidcIdToken.class); + idToken = new OidcAuthorizationCodeGrantAuthorization.IdToken(oidcIdToken.getToken().getTokenValue(), + oidcIdToken.getToken().getIssuedAt(), oidcIdToken.getToken().getExpiresAt(), + oidcIdToken.isInvalidated(), + new OAuth2AuthorizationGrantAuthorization.ClaimsHolder(oidcIdToken.getClaims())); + } + return idToken; + } + + static OAuth2DeviceCodeGrantAuthorization.DeviceCode extractDeviceCode(OAuth2Authorization authorization) { + OAuth2DeviceCodeGrantAuthorization.DeviceCode deviceCode = null; + if (authorization.getToken(OAuth2DeviceCode.class) != null) { + OAuth2Authorization.Token oauth2DeviceCode = authorization + .getToken(OAuth2DeviceCode.class); + deviceCode = new OAuth2DeviceCodeGrantAuthorization.DeviceCode(oauth2DeviceCode.getToken().getTokenValue(), + oauth2DeviceCode.getToken().getIssuedAt(), oauth2DeviceCode.getToken().getExpiresAt(), + oauth2DeviceCode.isInvalidated()); + } + return deviceCode; + } + + static OAuth2DeviceCodeGrantAuthorization.UserCode extractUserCode(OAuth2Authorization authorization) { + OAuth2DeviceCodeGrantAuthorization.UserCode userCode = null; + if (authorization.getToken(OAuth2UserCode.class) != null) { + OAuth2Authorization.Token oauth2UserCode = authorization.getToken(OAuth2UserCode.class); + userCode = new OAuth2DeviceCodeGrantAuthorization.UserCode(oauth2UserCode.getToken().getTokenValue(), + oauth2UserCode.getToken().getIssuedAt(), oauth2UserCode.getToken().getExpiresAt(), + oauth2UserCode.isInvalidated()); + } + return userCode; + } + + static RegisteredClient convertRegisteredClient(OAuth2RegisteredClient oauth2RegisteredClient) { + ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder() + .requireProofKey(oauth2RegisteredClient.getClientSettings().isRequireProofKey()) + .requireAuthorizationConsent(oauth2RegisteredClient.getClientSettings().isRequireAuthorizationConsent()); + if (StringUtils.hasText(oauth2RegisteredClient.getClientSettings().getJwkSetUrl())) { + clientSettingsBuilder.jwkSetUrl(oauth2RegisteredClient.getClientSettings().getJwkSetUrl()); + } + if (oauth2RegisteredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm() != null) { + clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm( + oauth2RegisteredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm()); + } + if (StringUtils.hasText(oauth2RegisteredClient.getClientSettings().getX509CertificateSubjectDN())) { + clientSettingsBuilder + .x509CertificateSubjectDN(oauth2RegisteredClient.getClientSettings().getX509CertificateSubjectDN()); + } + ClientSettings clientSettings = clientSettingsBuilder.build(); + + TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder(); + if (oauth2RegisteredClient.getTokenSettings().getAuthorizationCodeTimeToLive() != null) { + tokenSettingsBuilder.authorizationCodeTimeToLive( + oauth2RegisteredClient.getTokenSettings().getAuthorizationCodeTimeToLive()); + } + if (oauth2RegisteredClient.getTokenSettings().getAccessTokenTimeToLive() != null) { + tokenSettingsBuilder + .accessTokenTimeToLive(oauth2RegisteredClient.getTokenSettings().getAccessTokenTimeToLive()); + } + if (oauth2RegisteredClient.getTokenSettings().getAccessTokenFormat() != null) { + tokenSettingsBuilder.accessTokenFormat(oauth2RegisteredClient.getTokenSettings().getAccessTokenFormat()); + } + if (oauth2RegisteredClient.getTokenSettings().getDeviceCodeTimeToLive() != null) { + tokenSettingsBuilder + .deviceCodeTimeToLive(oauth2RegisteredClient.getTokenSettings().getDeviceCodeTimeToLive()); + } + tokenSettingsBuilder.reuseRefreshTokens(oauth2RegisteredClient.getTokenSettings().isReuseRefreshTokens()); + if (oauth2RegisteredClient.getTokenSettings().getRefreshTokenTimeToLive() != null) { + tokenSettingsBuilder + .refreshTokenTimeToLive(oauth2RegisteredClient.getTokenSettings().getRefreshTokenTimeToLive()); + } + if (oauth2RegisteredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) { + tokenSettingsBuilder + .idTokenSignatureAlgorithm(oauth2RegisteredClient.getTokenSettings().getIdTokenSignatureAlgorithm()); + } + tokenSettingsBuilder.x509CertificateBoundAccessTokens( + oauth2RegisteredClient.getTokenSettings().isX509CertificateBoundAccessTokens()); + TokenSettings tokenSettings = tokenSettingsBuilder.build(); + + RegisteredClient.Builder registeredClientBuilder = RegisteredClient.withId(oauth2RegisteredClient.getId()) + .clientId(oauth2RegisteredClient.getClientId()) + .clientIdIssuedAt(oauth2RegisteredClient.getClientIdIssuedAt()) + .clientSecret(oauth2RegisteredClient.getClientSecret()) + .clientSecretExpiresAt(oauth2RegisteredClient.getClientSecretExpiresAt()) + .clientName(oauth2RegisteredClient.getClientName()) + .clientAuthenticationMethods((clientAuthenticationMethods) -> clientAuthenticationMethods + .addAll(oauth2RegisteredClient.getClientAuthenticationMethods())) + .authorizationGrantTypes((authorizationGrantTypes) -> authorizationGrantTypes + .addAll(oauth2RegisteredClient.getAuthorizationGrantTypes())) + .clientSettings(clientSettings) + .tokenSettings(tokenSettings); + if (!CollectionUtils.isEmpty(oauth2RegisteredClient.getRedirectUris())) { + registeredClientBuilder.redirectUris((redirectUris) -> redirectUris.addAll(oauth2RegisteredClient.getRedirectUris())); + } + if (!CollectionUtils.isEmpty(oauth2RegisteredClient.getPostLogoutRedirectUris())) { + registeredClientBuilder.postLogoutRedirectUris((postLogoutRedirectUris) -> + postLogoutRedirectUris.addAll(oauth2RegisteredClient.getPostLogoutRedirectUris())); + } + if (!CollectionUtils.isEmpty(oauth2RegisteredClient.getScopes())) { + registeredClientBuilder.scopes((scopes) -> scopes.addAll(oauth2RegisteredClient.getScopes())); + } + + return registeredClientBuilder.build(); + } + + static OAuth2AuthorizationConsent convertOAuth2AuthorizationConsent(OAuth2UserConsent userConsent) { + return OAuth2AuthorizationConsent.withId(userConsent.getRegisteredClientId(), userConsent.getPrincipalName()) + .authorities((authorities) -> authorities.addAll(userConsent.getAuthorities())) + .build(); + } + + static void mapOAuth2AuthorizationGrantAuthorization( + OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization, + OAuth2Authorization.Builder builder) { + + if (authorizationGrantAuthorization instanceof OidcAuthorizationCodeGrantAuthorization authorizationGrant) { + mapOidcAuthorizationCodeGrantAuthorization(authorizationGrant, builder); + } + else if (authorizationGrantAuthorization instanceof OAuth2AuthorizationCodeGrantAuthorization authorizationGrant) { + mapOAuth2AuthorizationCodeGrantAuthorization(authorizationGrant, builder); + } + else if (authorizationGrantAuthorization instanceof OAuth2ClientCredentialsGrantAuthorization authorizationGrant) { + mapOAuth2ClientCredentialsGrantAuthorization(authorizationGrant, builder); + } + else if (authorizationGrantAuthorization instanceof OAuth2DeviceCodeGrantAuthorization authorizationGrant) { + mapOAuth2DeviceCodeGrantAuthorization(authorizationGrant, builder); + } + else if (authorizationGrantAuthorization instanceof OAuth2TokenExchangeGrantAuthorization authorizationGrant) { + mapOAuth2TokenExchangeGrantAuthorization(authorizationGrant, builder); + } + } + + static void mapOidcAuthorizationCodeGrantAuthorization( + OidcAuthorizationCodeGrantAuthorization authorizationCodeGrantAuthorization, + OAuth2Authorization.Builder builder) { + + mapOAuth2AuthorizationCodeGrantAuthorization(authorizationCodeGrantAuthorization, builder); + mapIdToken(authorizationCodeGrantAuthorization.getIdToken(), builder); + } + + static void mapOAuth2AuthorizationCodeGrantAuthorization( + OAuth2AuthorizationCodeGrantAuthorization authorizationCodeGrantAuthorization, + OAuth2Authorization.Builder builder) { + + builder.id(authorizationCodeGrantAuthorization.getId()) + .principalName(authorizationCodeGrantAuthorization.getPrincipalName()) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizedScopes(authorizationCodeGrantAuthorization.getAuthorizedScopes()) + .attribute(Principal.class.getName(), authorizationCodeGrantAuthorization.getPrincipal()) + .attribute(OAuth2AuthorizationRequest.class.getName(), + authorizationCodeGrantAuthorization.getAuthorizationRequest()); + if (StringUtils.hasText(authorizationCodeGrantAuthorization.getState())) { + builder.attribute(OAuth2ParameterNames.STATE, authorizationCodeGrantAuthorization.getState()); + } + + mapAuthorizationCode(authorizationCodeGrantAuthorization.getAuthorizationCode(), builder); + mapAccessToken(authorizationCodeGrantAuthorization.getAccessToken(), builder); + mapRefreshToken(authorizationCodeGrantAuthorization.getRefreshToken(), builder); + } + + static void mapOAuth2ClientCredentialsGrantAuthorization( + OAuth2ClientCredentialsGrantAuthorization clientCredentialsGrantAuthorization, + OAuth2Authorization.Builder builder) { + + builder.id(clientCredentialsGrantAuthorization.getId()) + .principalName(clientCredentialsGrantAuthorization.getPrincipalName()) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .authorizedScopes(clientCredentialsGrantAuthorization.getAuthorizedScopes()); + + mapAccessToken(clientCredentialsGrantAuthorization.getAccessToken(), builder); + } + + static void mapOAuth2DeviceCodeGrantAuthorization(OAuth2DeviceCodeGrantAuthorization deviceCodeGrantAuthorization, + OAuth2Authorization.Builder builder) { + + builder.id(deviceCodeGrantAuthorization.getId()) + .principalName(deviceCodeGrantAuthorization.getPrincipalName()) + .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) + .authorizedScopes(deviceCodeGrantAuthorization.getAuthorizedScopes()); + if (deviceCodeGrantAuthorization.getPrincipal() != null) { + builder.attribute(Principal.class.getName(), deviceCodeGrantAuthorization.getPrincipal()); + } + if (deviceCodeGrantAuthorization.getRequestedScopes() != null) { + builder.attribute(OAuth2ParameterNames.SCOPE, deviceCodeGrantAuthorization.getRequestedScopes()); + } + if (StringUtils.hasText(deviceCodeGrantAuthorization.getDeviceState())) { + builder.attribute(OAuth2ParameterNames.STATE, deviceCodeGrantAuthorization.getDeviceState()); + } + + mapAccessToken(deviceCodeGrantAuthorization.getAccessToken(), builder); + mapRefreshToken(deviceCodeGrantAuthorization.getRefreshToken(), builder); + mapDeviceCode(deviceCodeGrantAuthorization.getDeviceCode(), builder); + mapUserCode(deviceCodeGrantAuthorization.getUserCode(), builder); + } + + static void mapOAuth2TokenExchangeGrantAuthorization( + OAuth2TokenExchangeGrantAuthorization tokenExchangeGrantAuthorization, + OAuth2Authorization.Builder builder) { + + builder.id(tokenExchangeGrantAuthorization.getId()) + .principalName(tokenExchangeGrantAuthorization.getPrincipalName()) + .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) + .authorizedScopes(tokenExchangeGrantAuthorization.getAuthorizedScopes()); + + mapAccessToken(tokenExchangeGrantAuthorization.getAccessToken(), builder); + } + + static void mapAuthorizationCode(OAuth2AuthorizationCodeGrantAuthorization.AuthorizationCode authorizationCode, + OAuth2Authorization.Builder builder) { + if (authorizationCode == null) { + return; + } + OAuth2AuthorizationCode oauth2AuthorizationCode = new OAuth2AuthorizationCode(authorizationCode.getTokenValue(), + authorizationCode.getIssuedAt(), authorizationCode.getExpiresAt()); + builder.token(oauth2AuthorizationCode, (metadata) -> metadata + .put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, authorizationCode.isInvalidated())); + } + + static void mapAccessToken(OAuth2AuthorizationGrantAuthorization.AccessToken accessToken, + OAuth2Authorization.Builder builder) { + if (accessToken == null) { + return; + } + OAuth2AccessToken oauth2AccessToken = new OAuth2AccessToken(accessToken.getTokenType(), + accessToken.getTokenValue(), accessToken.getIssuedAt(), accessToken.getExpiresAt(), + accessToken.getScopes()); + builder.token(oauth2AccessToken, (metadata) -> { + metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, accessToken.isInvalidated()); + metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, accessToken.getClaims().getClaims()); + metadata.put(OAuth2TokenFormat.class.getName(), accessToken.getTokenFormat().getValue()); + }); + } + + static void mapRefreshToken(OAuth2AuthorizationGrantAuthorization.RefreshToken refreshToken, + OAuth2Authorization.Builder builder) { + if (refreshToken == null) { + return; + } + OAuth2RefreshToken oauth2RefreshToken = new OAuth2RefreshToken(refreshToken.getTokenValue(), + refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); + builder.token(oauth2RefreshToken, (metadata) -> metadata + .put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, refreshToken.isInvalidated())); + } + + static void mapIdToken(OidcAuthorizationCodeGrantAuthorization.IdToken idToken, + OAuth2Authorization.Builder builder) { + if (idToken == null) { + return; + } + OidcIdToken oidcIdToken = new OidcIdToken(idToken.getTokenValue(), idToken.getIssuedAt(), + idToken.getExpiresAt(), idToken.getClaims().getClaims()); + builder.token(oidcIdToken, (metadata) -> { + metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, idToken.isInvalidated()); + metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims().getClaims()); + }); + } + + static void mapDeviceCode(OAuth2DeviceCodeGrantAuthorization.DeviceCode deviceCode, + OAuth2Authorization.Builder builder) { + if (deviceCode == null) { + return; + } + OAuth2DeviceCode oauth2DeviceCode = new OAuth2DeviceCode(deviceCode.getTokenValue(), deviceCode.getIssuedAt(), + deviceCode.getExpiresAt()); + builder.token(oauth2DeviceCode, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, + deviceCode.isInvalidated())); + } + + static void mapUserCode(OAuth2DeviceCodeGrantAuthorization.UserCode userCode, OAuth2Authorization.Builder builder) { + if (userCode == null) { + return; + } + OAuth2UserCode oauth2UserCode = new OAuth2UserCode(userCode.getTokenValue(), userCode.getIssuedAt(), + userCode.getExpiresAt()); + builder.token(oauth2UserCode, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, + userCode.isInvalidated())); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationConsentService.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationConsentService.java new file mode 100644 index 000000000..b811d23c3 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationConsentService.java @@ -0,0 +1,62 @@ +/* + * 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.data.redis; + +import org.springframework.lang.Nullable; +import sample.data.redis.model.OAuth2UserConsent; +import sample.data.redis.repository.OAuth2UserConsentRepository; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.util.Assert; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class RedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { + + private final OAuth2UserConsentRepository userConsentRepository; + + public RedisOAuth2AuthorizationConsentService(OAuth2UserConsentRepository userConsentRepository) { + Assert.notNull(userConsentRepository, "userConsentRepository cannot be null"); + this.userConsentRepository = userConsentRepository; + } + + @Override + public void save(OAuth2AuthorizationConsent authorizationConsent) { + Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); + OAuth2UserConsent oauth2UserConsent = ModelMapper.convertOAuth2UserConsent(authorizationConsent); + this.userConsentRepository.save(oauth2UserConsent); + } + + @Override + public void remove(OAuth2AuthorizationConsent authorizationConsent) { + Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); + this.userConsentRepository.deleteByRegisteredClientIdAndPrincipalName( + authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); + } + + @Nullable + @Override + public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { + Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); + Assert.hasText(principalName, "principalName cannot be empty"); + OAuth2UserConsent oauth2UserConsent = this.userConsentRepository + .findByRegisteredClientIdAndPrincipalName(registeredClientId, principalName); + return oauth2UserConsent != null ? ModelMapper.convertOAuth2AuthorizationConsent(oauth2UserConsent) : null; + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationService.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationService.java new file mode 100644 index 000000000..f1bfd7b54 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisOAuth2AuthorizationService.java @@ -0,0 +1,127 @@ +/* + * 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.data.redis; + +import sample.data.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository; + +import org.springframework.lang.Nullable; +import sample.data.redis.model.OAuth2AuthorizationGrantAuthorization; + +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.util.Assert; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService { + + private final RegisteredClientRepository registeredClientRepository; + + private final OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository; + + public RedisOAuth2AuthorizationService(RegisteredClientRepository registeredClientRepository, + OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) { + Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); + Assert.notNull(authorizationGrantAuthorizationRepository, + "authorizationGrantAuthorizationRepository cannot be null"); + this.registeredClientRepository = registeredClientRepository; + this.authorizationGrantAuthorizationRepository = authorizationGrantAuthorizationRepository; + } + + @Override + public void save(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization = ModelMapper + .convertOAuth2AuthorizationGrantAuthorization(authorization); + this.authorizationGrantAuthorizationRepository.save(authorizationGrantAuthorization); + } + + @Override + public void remove(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + this.authorizationGrantAuthorizationRepository.deleteById(authorization.getId()); + } + + @Nullable + @Override + public OAuth2Authorization findById(String id) { + Assert.hasText(id, "id cannot be empty"); + return this.authorizationGrantAuthorizationRepository.findById(id) + .map(this::toOAuth2Authorization) + .orElse(null); + } + + @Nullable + @Override + public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { + Assert.hasText(token, "token cannot be empty"); + OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization = null; + if (tokenType == null) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByStateOrAuthorizationCode_TokenValueOrAccessToken_TokenValueOrRefreshToken_TokenValueOrIdToken_TokenValueOrDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue( + token, token, token, token, token, token, token, token); + } + else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository.findByState(token); + if (authorizationGrantAuthorization == null) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByDeviceState(token); + } + } + else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByAuthorizationCode_TokenValue(token); + } + else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByAccessToken_TokenValue(token); + } + else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByIdToken_TokenValue(token); + } + else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByRefreshToken_TokenValue(token); + } + else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByUserCode_TokenValue(token); + } + else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) { + authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository + .findByDeviceCode_TokenValue(token); + } + return authorizationGrantAuthorization != null ? toOAuth2Authorization(authorizationGrantAuthorization) : null; + } + + private OAuth2Authorization toOAuth2Authorization( + OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization) { + RegisteredClient registeredClient = this.registeredClientRepository + .findById(authorizationGrantAuthorization.getRegisteredClientId()); + OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient); + ModelMapper.mapOAuth2AuthorizationGrantAuthorization(authorizationGrantAuthorization, builder); + return builder.build(); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisRegisteredClientRepository.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisRegisteredClientRepository.java new file mode 100644 index 000000000..225ac3766 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/RedisRegisteredClientRepository.java @@ -0,0 +1,62 @@ +/* + * 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.data.redis; + +import sample.data.redis.model.OAuth2RegisteredClient; + +import org.springframework.lang.Nullable; + +import sample.data.redis.repository.OAuth2RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.util.Assert; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class RedisRegisteredClientRepository implements RegisteredClientRepository { + + private final OAuth2RegisteredClientRepository registeredClientRepository; + + public RedisRegisteredClientRepository(OAuth2RegisteredClientRepository registeredClientRepository) { + Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); + this.registeredClientRepository = registeredClientRepository; + } + + @Override + public void save(RegisteredClient registeredClient) { + Assert.notNull(registeredClient, "registeredClient cannot be null"); + OAuth2RegisteredClient oauth2RegisteredClient = ModelMapper.convertOAuth2RegisteredClient(registeredClient); + this.registeredClientRepository.save(oauth2RegisteredClient); + } + + @Nullable + @Override + public RegisteredClient findById(String id) { + Assert.hasText(id, "id cannot be empty"); + return this.registeredClientRepository.findById(id).map(ModelMapper::convertRegisteredClient).orElse(null); + } + + @Nullable + @Override + public RegisteredClient findByClientId(String clientId) { + Assert.hasText(clientId, "clientId cannot be empty"); + OAuth2RegisteredClient oauth2RegisteredClient = this.registeredClientRepository.findByClientId(clientId); + return oauth2RegisteredClient != null ? ModelMapper.convertRegisteredClient(oauth2RegisteredClient) : null; + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToClaimsHolderConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToClaimsHolderConverter.java new file mode 100644 index 000000000..c6394731c --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToClaimsHolderConverter.java @@ -0,0 +1,52 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import sample.data.redis.model.OAuth2AuthorizationGrantAuthorization; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@ReadingConverter +public class BytesToClaimsHolderConverter + implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public BytesToClaimsHolderConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper + .registerModules(SecurityJackson2Modules.getModules(BytesToClaimsHolderConverter.class.getClassLoader())); + objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); + objectMapper.addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, + OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class); + } + + @Override + public OAuth2AuthorizationGrantAuthorization.ClaimsHolder convert(byte[] value) { + return this.serializer.deserialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java new file mode 100644 index 000000000..63e9ecadf --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java @@ -0,0 +1,49 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@ReadingConverter +public class BytesToOAuth2AuthorizationRequestConverter implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public BytesToOAuth2AuthorizationRequestConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModules( + SecurityJackson2Modules.getModules(BytesToOAuth2AuthorizationRequestConverter.class.getClassLoader())); + objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); + } + + @Override + public OAuth2AuthorizationRequest convert(byte[] value) { + return this.serializer.deserialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java new file mode 100644 index 000000000..506f105d8 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java @@ -0,0 +1,48 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.jackson2.SecurityJackson2Modules; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@ReadingConverter +public class BytesToUsernamePasswordAuthenticationTokenConverter + implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public BytesToUsernamePasswordAuthenticationTokenConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModules(SecurityJackson2Modules + .getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); + } + + @Override + public UsernamePasswordAuthenticationToken convert(byte[] value) { + return this.serializer.deserialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderMixin.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderMixin.java new file mode 100644 index 000000000..51314f50f --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderMixin.java @@ -0,0 +1,40 @@ +/* + * 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.data.redis.convert; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class ClaimsHolderMixin { + + @JsonCreator + ClaimsHolderMixin(@JsonProperty("claims") Map claims) { + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderToBytesConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderToBytesConverter.java new file mode 100644 index 000000000..ce7da4fd6 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/ClaimsHolderToBytesConverter.java @@ -0,0 +1,52 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import sample.data.redis.model.OAuth2AuthorizationGrantAuthorization; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@WritingConverter +public class ClaimsHolderToBytesConverter + implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public ClaimsHolderToBytesConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper + .registerModules(SecurityJackson2Modules.getModules(ClaimsHolderToBytesConverter.class.getClassLoader())); + objectMapper.registerModules(new OAuth2AuthorizationServerJackson2Module()); + objectMapper.addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, + OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class); + } + + @Override + public byte[] convert(OAuth2AuthorizationGrantAuthorization.ClaimsHolder value) { + return this.serializer.serialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java new file mode 100644 index 000000000..29c3eb07b --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java @@ -0,0 +1,49 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@WritingConverter +public class OAuth2AuthorizationRequestToBytesConverter implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public OAuth2AuthorizationRequestToBytesConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModules( + SecurityJackson2Modules.getModules(OAuth2AuthorizationRequestToBytesConverter.class.getClassLoader())); + objectMapper.registerModules(new OAuth2AuthorizationServerJackson2Module()); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); + } + + @Override + public byte[] convert(OAuth2AuthorizationRequest value) { + return this.serializer.serialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java new file mode 100644 index 000000000..942ff14fc --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java @@ -0,0 +1,48 @@ +/* + * 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.data.redis.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.jackson2.SecurityJackson2Modules; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@WritingConverter +public class UsernamePasswordAuthenticationTokenToBytesConverter + implements Converter { + + private final Jackson2JsonRedisSerializer serializer; + + public UsernamePasswordAuthenticationTokenToBytesConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModules(SecurityJackson2Modules + .getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())); + this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); + } + + @Override + public byte[] convert(UsernamePasswordAuthenticationToken value) { + return this.serializer.serialize(value); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationCodeGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationCodeGrantAuthorization.java new file mode 100644 index 000000000..5afb92bd8 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationCodeGrantAuthorization.java @@ -0,0 +1,75 @@ +/* + * 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.data.redis.model; + +import java.security.Principal; +import java.time.Instant; +import java.util.Set; + +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class OAuth2AuthorizationCodeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization { + + private final Principal principal; + + private final OAuth2AuthorizationRequest authorizationRequest; + + private final AuthorizationCode authorizationCode; + + @Indexed + private final String state; // Used to correlate the request during the authorization + // consent flow + + public OAuth2AuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal, + OAuth2AuthorizationRequest authorizationRequest, AuthorizationCode authorizationCode, String state) { + super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken); + this.principal = principal; + this.authorizationRequest = authorizationRequest; + this.authorizationCode = authorizationCode; + this.state = state; + } + + public Principal getPrincipal() { + return this.principal; + } + + public OAuth2AuthorizationRequest getAuthorizationRequest() { + return this.authorizationRequest; + } + + public AuthorizationCode getAuthorizationCode() { + return this.authorizationCode; + } + + public String getState() { + return this.state; + } + + public static class AuthorizationCode extends AbstractToken { + + public AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) { + super(tokenValue, issuedAt, expiresAt, invalidated); + } + + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationGrantAuthorization.java new file mode 100644 index 000000000..670698756 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2AuthorizationGrantAuthorization.java @@ -0,0 +1,178 @@ +/* + * 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.data.redis.model; + +import java.time.Instant; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@RedisHash("oauth2_authorization") +public abstract class OAuth2AuthorizationGrantAuthorization { + + @Id + private final String id; + + private final String registeredClientId; + + private final String principalName; + + private final Set authorizedScopes; + + private final AccessToken accessToken; + + private final RefreshToken refreshToken; + + protected OAuth2AuthorizationGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken, RefreshToken refreshToken) { + this.id = id; + this.registeredClientId = registeredClientId; + this.principalName = principalName; + this.authorizedScopes = authorizedScopes; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public String getId() { + return this.id; + } + + public String getRegisteredClientId() { + return this.registeredClientId; + } + + public String getPrincipalName() { + return this.principalName; + } + + public Set getAuthorizedScopes() { + return this.authorizedScopes; + } + + public AccessToken getAccessToken() { + return this.accessToken; + } + + public RefreshToken getRefreshToken() { + return this.refreshToken; + } + + protected abstract static class AbstractToken { + + @Indexed + private final String tokenValue; + + private final Instant issuedAt; + + private final Instant expiresAt; + + private final boolean invalidated; + + protected AbstractToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) { + this.tokenValue = tokenValue; + this.issuedAt = issuedAt; + this.expiresAt = expiresAt; + this.invalidated = invalidated; + } + + public String getTokenValue() { + return this.tokenValue; + } + + public Instant getIssuedAt() { + return this.issuedAt; + } + + public Instant getExpiresAt() { + return this.expiresAt; + } + + public boolean isInvalidated() { + return this.invalidated; + } + + } + + public static class ClaimsHolder { + + private final Map claims; + + public ClaimsHolder(Map claims) { + this.claims = claims; + } + + public Map getClaims() { + return this.claims; + } + + } + + public static class AccessToken extends AbstractToken { + + private final OAuth2AccessToken.TokenType tokenType; + + private final Set scopes; + + private final OAuth2TokenFormat tokenFormat; + + private final ClaimsHolder claims; + + public AccessToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated, + OAuth2AccessToken.TokenType tokenType, Set scopes, OAuth2TokenFormat tokenFormat, + ClaimsHolder claims) { + super(tokenValue, issuedAt, expiresAt, invalidated); + this.tokenType = tokenType; + this.scopes = scopes; + this.tokenFormat = tokenFormat; + this.claims = claims; + } + + public OAuth2AccessToken.TokenType getTokenType() { + return this.tokenType; + } + + public Set getScopes() { + return this.scopes; + } + + public OAuth2TokenFormat getTokenFormat() { + return this.tokenFormat; + } + + public ClaimsHolder getClaims() { + return this.claims; + } + + } + + public static class RefreshToken extends AbstractToken { + + public RefreshToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) { + super(tokenValue, issuedAt, expiresAt, invalidated); + } + + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2ClientCredentialsGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2ClientCredentialsGrantAuthorization.java new file mode 100644 index 000000000..9126d876d --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2ClientCredentialsGrantAuthorization.java @@ -0,0 +1,31 @@ +/* + * 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.data.redis.model; + +import java.util.Set; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class OAuth2ClientCredentialsGrantAuthorization extends OAuth2AuthorizationGrantAuthorization { + + public OAuth2ClientCredentialsGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken) { + super(id, registeredClientId, principalName, authorizedScopes, accessToken, null); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2DeviceCodeGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2DeviceCodeGrantAuthorization.java new file mode 100644 index 000000000..e41947c44 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2DeviceCodeGrantAuthorization.java @@ -0,0 +1,89 @@ +/* + * 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.data.redis.model; + +import java.security.Principal; +import java.time.Instant; +import java.util.Set; + +import org.springframework.data.redis.core.index.Indexed; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class OAuth2DeviceCodeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization { + + private final Principal principal; + + private final DeviceCode deviceCode; + + private final UserCode userCode; + + private final Set requestedScopes; + + @Indexed + private final String deviceState; // Used to correlate the request during the + // authorization consent flow + + public OAuth2DeviceCodeGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal, + DeviceCode deviceCode, UserCode userCode, Set requestedScopes, String deviceState) { + super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken); + this.principal = principal; + this.deviceCode = deviceCode; + this.userCode = userCode; + this.requestedScopes = requestedScopes; + this.deviceState = deviceState; + } + + public Principal getPrincipal() { + return this.principal; + } + + public DeviceCode getDeviceCode() { + return this.deviceCode; + } + + public UserCode getUserCode() { + return this.userCode; + } + + public Set getRequestedScopes() { + return this.requestedScopes; + } + + public String getDeviceState() { + return this.deviceState; + } + + public static class DeviceCode extends AbstractToken { + + public DeviceCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) { + super(tokenValue, issuedAt, expiresAt, invalidated); + } + + } + + public static class UserCode extends AbstractToken { + + public UserCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) { + super(tokenValue, issuedAt, expiresAt, invalidated); + } + + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2RegisteredClient.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2RegisteredClient.java new file mode 100644 index 000000000..e78fa402a --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2RegisteredClient.java @@ -0,0 +1,248 @@ +/* + * 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.data.redis.model; + +import java.time.Duration; +import java.time.Instant; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@RedisHash("oauth2_registered_client") +public class OAuth2RegisteredClient { + + @Id + private final String id; + + @Indexed + private final String clientId; + + private final Instant clientIdIssuedAt; + + private final String clientSecret; + + private final Instant clientSecretExpiresAt; + + private final String clientName; + + private final Set clientAuthenticationMethods; + + private final Set authorizationGrantTypes; + + private final Set redirectUris; + + private final Set postLogoutRedirectUris; + + private final Set scopes; + + private final ClientSettings clientSettings; + + private final TokenSettings tokenSettings; + + public OAuth2RegisteredClient(String id, String clientId, Instant clientIdIssuedAt, String clientSecret, + Instant clientSecretExpiresAt, String clientName, + Set clientAuthenticationMethods, + Set authorizationGrantTypes, Set redirectUris, + Set postLogoutRedirectUris, Set scopes, ClientSettings clientSettings, + TokenSettings tokenSettings) { + this.id = id; + this.clientId = clientId; + this.clientIdIssuedAt = clientIdIssuedAt; + this.clientSecret = clientSecret; + this.clientSecretExpiresAt = clientSecretExpiresAt; + this.clientName = clientName; + this.clientAuthenticationMethods = clientAuthenticationMethods; + this.authorizationGrantTypes = authorizationGrantTypes; + this.redirectUris = redirectUris; + this.postLogoutRedirectUris = postLogoutRedirectUris; + this.scopes = scopes; + this.clientSettings = clientSettings; + this.tokenSettings = tokenSettings; + } + + public String getId() { + return this.id; + } + + public String getClientId() { + return this.clientId; + } + + public Instant getClientIdIssuedAt() { + return this.clientIdIssuedAt; + } + + public String getClientSecret() { + return this.clientSecret; + } + + public Instant getClientSecretExpiresAt() { + return this.clientSecretExpiresAt; + } + + public String getClientName() { + return this.clientName; + } + + public Set getClientAuthenticationMethods() { + return this.clientAuthenticationMethods; + } + + public Set getAuthorizationGrantTypes() { + return this.authorizationGrantTypes; + } + + public Set getRedirectUris() { + return this.redirectUris; + } + + public Set getPostLogoutRedirectUris() { + return this.postLogoutRedirectUris; + } + + public Set getScopes() { + return this.scopes; + } + + public ClientSettings getClientSettings() { + return this.clientSettings; + } + + public TokenSettings getTokenSettings() { + return this.tokenSettings; + } + + public static class ClientSettings { + + private final boolean requireProofKey; + + private final boolean requireAuthorizationConsent; + + private final String jwkSetUrl; + + private final JwsAlgorithm tokenEndpointAuthenticationSigningAlgorithm; + + private final String x509CertificateSubjectDN; + + public ClientSettings(boolean requireProofKey, boolean requireAuthorizationConsent, String jwkSetUrl, + JwsAlgorithm tokenEndpointAuthenticationSigningAlgorithm, String x509CertificateSubjectDN) { + this.requireProofKey = requireProofKey; + this.requireAuthorizationConsent = requireAuthorizationConsent; + this.jwkSetUrl = jwkSetUrl; + this.tokenEndpointAuthenticationSigningAlgorithm = tokenEndpointAuthenticationSigningAlgorithm; + this.x509CertificateSubjectDN = x509CertificateSubjectDN; + } + + public boolean isRequireProofKey() { + return this.requireProofKey; + } + + public boolean isRequireAuthorizationConsent() { + return this.requireAuthorizationConsent; + } + + public String getJwkSetUrl() { + return this.jwkSetUrl; + } + + public JwsAlgorithm getTokenEndpointAuthenticationSigningAlgorithm() { + return this.tokenEndpointAuthenticationSigningAlgorithm; + } + + public String getX509CertificateSubjectDN() { + return this.x509CertificateSubjectDN; + } + + } + + public static class TokenSettings { + + private final Duration authorizationCodeTimeToLive; + + private final Duration accessTokenTimeToLive; + + private final OAuth2TokenFormat accessTokenFormat; + + private final Duration deviceCodeTimeToLive; + + private final boolean reuseRefreshTokens; + + private final Duration refreshTokenTimeToLive; + + private final SignatureAlgorithm idTokenSignatureAlgorithm; + + private final boolean x509CertificateBoundAccessTokens; + + public TokenSettings(Duration authorizationCodeTimeToLive, Duration accessTokenTimeToLive, + OAuth2TokenFormat accessTokenFormat, Duration deviceCodeTimeToLive, boolean reuseRefreshTokens, + Duration refreshTokenTimeToLive, SignatureAlgorithm idTokenSignatureAlgorithm, + boolean x509CertificateBoundAccessTokens) { + this.authorizationCodeTimeToLive = authorizationCodeTimeToLive; + this.accessTokenTimeToLive = accessTokenTimeToLive; + this.accessTokenFormat = accessTokenFormat; + this.deviceCodeTimeToLive = deviceCodeTimeToLive; + this.reuseRefreshTokens = reuseRefreshTokens; + this.refreshTokenTimeToLive = refreshTokenTimeToLive; + this.idTokenSignatureAlgorithm = idTokenSignatureAlgorithm; + this.x509CertificateBoundAccessTokens = x509CertificateBoundAccessTokens; + } + + public Duration getAuthorizationCodeTimeToLive() { + return this.authorizationCodeTimeToLive; + } + + public Duration getAccessTokenTimeToLive() { + return this.accessTokenTimeToLive; + } + + public OAuth2TokenFormat getAccessTokenFormat() { + return this.accessTokenFormat; + } + + public Duration getDeviceCodeTimeToLive() { + return this.deviceCodeTimeToLive; + } + + public boolean isReuseRefreshTokens() { + return this.reuseRefreshTokens; + } + + public Duration getRefreshTokenTimeToLive() { + return this.refreshTokenTimeToLive; + } + + public SignatureAlgorithm getIdTokenSignatureAlgorithm() { + return this.idTokenSignatureAlgorithm; + } + + public boolean isX509CertificateBoundAccessTokens() { + return this.x509CertificateBoundAccessTokens; + } + + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2TokenExchangeGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2TokenExchangeGrantAuthorization.java new file mode 100644 index 000000000..e36000881 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2TokenExchangeGrantAuthorization.java @@ -0,0 +1,31 @@ +/* + * 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.data.redis.model; + +import java.util.Set; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class OAuth2TokenExchangeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization { + + public OAuth2TokenExchangeGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken) { + super(id, registeredClientId, principalName, authorizedScopes, accessToken, null); + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2UserConsent.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2UserConsent.java new file mode 100644 index 000000000..bae8ccc16 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OAuth2UserConsent.java @@ -0,0 +1,67 @@ +/* + * 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.data.redis.model; + +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.security.core.GrantedAuthority; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@RedisHash("oauth2_authorization_consent") +public class OAuth2UserConsent { + + @Id + private final String id; + + @Indexed + private final String registeredClientId; + + @Indexed + private final String principalName; + + private final Set authorities; + + public OAuth2UserConsent(String id, String registeredClientId, String principalName, + Set authorities) { + this.id = id; + this.registeredClientId = registeredClientId; + this.principalName = principalName; + this.authorities = authorities; + } + + public String getId() { + return this.id; + } + + public String getRegisteredClientId() { + return this.registeredClientId; + } + + public String getPrincipalName() { + return this.principalName; + } + + public Set getAuthorities() { + return this.authorities; + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OidcAuthorizationCodeGrantAuthorization.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OidcAuthorizationCodeGrantAuthorization.java new file mode 100644 index 000000000..320e9e922 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/model/OidcAuthorizationCodeGrantAuthorization.java @@ -0,0 +1,61 @@ +/* + * 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.data.redis.model; + +import java.security.Principal; +import java.time.Instant; +import java.util.Set; + +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; + +/** + * @author Joe Grandja + * @since 1.4 + */ +public class OidcAuthorizationCodeGrantAuthorization extends OAuth2AuthorizationCodeGrantAuthorization { + + private final IdToken idToken; + + public OidcAuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName, + Set authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal, + OAuth2AuthorizationRequest authorizationRequest, AuthorizationCode authorizationCode, String state, + IdToken idToken) { + super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken, principal, + authorizationRequest, authorizationCode, state); + this.idToken = idToken; + } + + public IdToken getIdToken() { + return this.idToken; + } + + public static class IdToken extends AbstractToken { + + private final ClaimsHolder claims; + + public IdToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated, + ClaimsHolder claims) { + super(tokenValue, issuedAt, expiresAt, invalidated); + this.claims = claims; + } + + public ClaimsHolder getClaims() { + return this.claims; + } + + } + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java new file mode 100644 index 000000000..554a3b034 --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java @@ -0,0 +1,53 @@ +/* + * 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.data.redis.repository; + +import org.springframework.data.repository.CrudRepository; +import sample.data.redis.model.OAuth2AuthorizationCodeGrantAuthorization; +import sample.data.redis.model.OAuth2AuthorizationGrantAuthorization; +import sample.data.redis.model.OAuth2DeviceCodeGrantAuthorization; +import sample.data.redis.model.OidcAuthorizationCodeGrantAuthorization; +import org.springframework.stereotype.Repository; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@Repository +public interface OAuth2AuthorizationGrantAuthorizationRepository + extends CrudRepository { + + T findByState(String token); + + T findByAuthorizationCode_TokenValue(String token); + + T findByAccessToken_TokenValue(String token); + + T findByRefreshToken_TokenValue(String token); + + T findByIdToken_TokenValue(String token); + + T findByDeviceState(String token); + + T findByDeviceCode_TokenValue(String token); + + T findByUserCode_TokenValue(String token); + + T findByStateOrAuthorizationCode_TokenValueOrAccessToken_TokenValueOrRefreshToken_TokenValueOrIdToken_TokenValueOrDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue( + String state, String authorizationCode, String accessToken, String refreshToken, String idToken, + String deviceState, String deviceCode, String userCode); + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2RegisteredClientRepository.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2RegisteredClientRepository.java new file mode 100644 index 000000000..f2f535fbc --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2RegisteredClientRepository.java @@ -0,0 +1,32 @@ +/* + * 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.data.redis.repository; + +import sample.data.redis.model.OAuth2RegisteredClient; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@Repository +public interface OAuth2RegisteredClientRepository extends CrudRepository { + + OAuth2RegisteredClient findByClientId(String clientId); + +} diff --git a/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2UserConsentRepository.java b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2UserConsentRepository.java new file mode 100644 index 000000000..cadab752d --- /dev/null +++ b/samples/demo-authorizationserver/src/main/java/sample/data/redis/repository/OAuth2UserConsentRepository.java @@ -0,0 +1,33 @@ +/* + * 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.data.redis.repository; + +import org.springframework.data.repository.CrudRepository; +import sample.data.redis.model.OAuth2UserConsent; +import org.springframework.stereotype.Repository; + +/** + * @author Joe Grandja + * @since 1.4 + */ +@Repository +public interface OAuth2UserConsentRepository extends CrudRepository { + + OAuth2UserConsent findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName); + + void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName); + +} diff --git a/samples/demo-authorizationserver/src/main/resources/application-redis.yml b/samples/demo-authorizationserver/src/main/resources/application-redis.yml new file mode 100644 index 000000000..d75901d4c --- /dev/null +++ b/samples/demo-authorizationserver/src/main/resources/application-redis.yml @@ -0,0 +1,9 @@ +spring: + data: + redis: + # Redis server host, default localhost + host: localhost + # Redis server port, default 6379 + port: 6379 + repositories: + enabled: true