Add How-to: Implement core services with Redis

Closes gh-1019
This commit is contained in:
Joe Grandja
2024-09-12 17:02:41 -04:00
parent c942ed7e43
commit fffe227d75
26 changed files with 2237 additions and 0 deletions

View File

@@ -11,5 +11,6 @@
** xref:guides/how-to-multitenancy.adoc[]
** xref:guides/how-to-userinfo.adoc[]
** xref:guides/how-to-jpa.adoc[]
** xref:guides/how-to-redis.adoc[]
** xref:guides/how-to-custom-claims-authorities.adoc[]
** xref:guides/how-to-dynamic-client-registration.adoc[]

View File

@@ -0,0 +1,225 @@
[[how-to-redis]]
= How-to: Implement core services with Redis
:index-link: ../how-to.html
:docs-dir: ..
This guide shows how to implement the xref:core-model-components.adoc[core services] of xref:index.adoc[Spring Authorization Server] with https://redis.io/[Redis].
The purpose of this guide is to provide a starting point for implementing these services yourself, with the intention that you can make modifications to suit your needs.
* xref:guides/how-to-redis.adoc#define-entity-model[Define the entity model]
* xref:guides/how-to-redis.adoc#create-spring-data-repositories[Create Spring Data repositories]
* xref:guides/how-to-redis.adoc#implement-core-services[Implement core services]
* xref:guides/how-to-redis.adoc#configure-core-services[Configure core services]
TIP: The code samples provided in this guide are located in the https://github.com/spring-projects/spring-authorization-server/tree/main/docs/src/main/java/sample[documentation samples] directory under the *_redis_* subdirectory.
[[define-entity-model]]
== Define the entity model
The following defines the entity model representation for the `RegisteredClient`, `OAuth2Authorization` and `OAuth2AuthorizationConsent` domain classes.
* xref:guides/how-to-redis.adoc#registered-client-entity[Registered Client Entity]
* xref:guides/how-to-redis.adoc#authorization-grant-entity[Authorization Grant _Base_ Entity]
* xref:guides/how-to-redis.adoc#oauth2-authorization-code-grant-entity[Authorization Code Grant Entity (OAuth 2.0)]
* xref:guides/how-to-redis.adoc#oidc-authorization-code-grant-entity[Authorization Code Grant Entity (OpenID Connect 1.0)]
* xref:guides/how-to-redis.adoc#client-credentials-grant-entity[Client Credentials Grant Entity]
* xref:guides/how-to-redis.adoc#device-code-grant-entity[Device Code Grant Entity]
* xref:guides/how-to-redis.adoc#token-exchange-grant-entity[Token Exchange Grant Entity]
* xref:guides/how-to-redis.adoc#authorization-consent-entity[Authorization Consent Entity]
[[registered-client-entity]]
=== Registered Client Entity
The following listing shows the `OAuth2RegisteredClient` entity, which is used to persist information mapped from the xref:core-model-components.adoc#registered-client[`RegisteredClient`] domain class.
.OAuth2RegisteredClient Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2RegisteredClient.java[]
----
TIP: Click on the "Expand folded text" icon in the code sample above to display the full example.
[[authorization-grant-entity]]
=== Authorization Grant _Base_ Entity
The entity model for the xref:core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain class is designed with a class hierarchy based on authorization grant type.
The following listing shows the `OAuth2AuthorizationGrantAuthorization` _base_ entity, which defines common attributes for each authorization grant type.
.OAuth2AuthorizationGrantAuthorization _Base_ Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2AuthorizationGrantAuthorization.java[]
----
[[oauth2-authorization-code-grant-entity]]
=== Authorization Code Grant Entity (OAuth 2.0)
The following listing shows the `OAuth2AuthorizationCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, and defines additional attributes for the OAuth 2.0 `authorization_code` grant type.
.OAuth2AuthorizationCodeGrantAuthorization Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java[]
----
[[oidc-authorization-code-grant-entity]]
=== Authorization Code Grant Entity (OpenID Connect 1.0)
The following listing shows the `OidcAuthorizationCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationCodeGrantAuthorization`, and defines additional attributes for the OpenID Connect 1.0 `authorization_code` grant type.
.OidcAuthorizationCodeGrantAuthorization Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OidcAuthorizationCodeGrantAuthorization.java[]
----
[[client-credentials-grant-entity]]
=== Client Credentials Grant Entity
The following listing shows the `OAuth2ClientCredentialsGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, for the `client_credentials` grant type.
.OAuth2ClientCredentialsGrantAuthorization Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java[]
----
[[device-code-grant-entity]]
=== Device Code Grant Entity
The following listing shows the `OAuth2DeviceCodeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, and defines additional attributes for the `urn:ietf:params:oauth:grant-type:device_code` grant type.
.OAuth2DeviceCodeGrantAuthorization Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2DeviceCodeGrantAuthorization.java[]
----
[[token-exchange-grant-entity]]
=== Token Exchange Grant Entity
The following listing shows the `OAuth2TokenExchangeGrantAuthorization` entity, which extends `OAuth2AuthorizationGrantAuthorization`, for the `urn:ietf:params:oauth:grant-type:token-exchange` grant type.
.OAuth2TokenExchangeGrantAuthorization Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2TokenExchangeGrantAuthorization.java[]
----
[[authorization-consent-entity]]
=== Authorization Consent Entity
The following listing shows the `OAuth2UserConsent` entity, which is used to persist information mapped from the xref:core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain class.
.OAuth2UserConsent Entity
[source,java]
----
include::{examples-dir}/main/java/sample/redis/entity/OAuth2UserConsent.java[]
----
[[create-spring-data-repositories]]
== Create Spring Data repositories
By closely examining the interfaces of each core service and reviewing the `Jdbc` implementations, we can derive a minimal set of queries needed for supporting a Redis version of each interface.
* xref:guides/how-to-redis.adoc#registered-client-repository[Registered Client Repository]
* xref:guides/how-to-redis.adoc#authorization-grant-repository[Authorization Grant Repository]
* xref:guides/how-to-redis.adoc#authorization-consent-repository[Authorization Consent Repository]
[[registered-client-repository]]
=== Registered Client Repository
The following listing shows the `OAuth2RegisteredClientRepository`, which is able to find a xref:guides/how-to-redis.adoc#registered-client-entity[`OAuth2RegisteredClient`] by the `id` and `clientId` fields.
.OAuth2RegisteredClientRepository
[source,java]
----
include::{examples-dir}/main/java/sample/redis/repository/OAuth2RegisteredClientRepository.java[]
----
[[authorization-grant-repository]]
=== Authorization Grant Repository
The following listing shows the `OAuth2AuthorizationGrantAuthorizationRepository`, which is able to find an xref:guides/how-to-redis.adoc#authorization-grant-entity[`OAuth2AuthorizationGrantAuthorization`] by the `id` field as well as by `state`, `authorizationCode`, `accessToken`, `refreshToken`, `idToken`, `deviceState`, `userCode` and `deviceCode` values.
.OAuth2AuthorizationGrantAuthorizationRepository
[source,java]
----
include::{examples-dir}/main/java/sample/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java[]
----
[[authorization-consent-repository]]
=== Authorization Consent Repository
The following listing shows the `OAuth2UserConsentRepository`, which is able to find and delete an xref:guides/how-to-redis.adoc#authorization-consent-entity[`OAuth2UserConsent`] by the `registeredClientId` and `principalName` fields that form the composite primary key.
.OAuth2UserConsentRepository
[source,java]
----
include::{examples-dir}/main/java/sample/redis/repository/OAuth2UserConsentRepository.java[]
----
[[implement-core-services]]
== Implement core services
With the above xref:guides/how-to-redis.adoc#define-entity-model[entities] and xref:guides/how-to-redis.adoc#create-spring-data-repositories[repositories], we can begin implementing the core services.
TIP: The core services make use of the `ModelMapper` utility class for converting to and from the domain object (e.g. `RegisteredClient`) to the entity model representation (e.g. `OAuth2RegisteredClient`).
* xref:guides/how-to-redis.adoc#redis-registered-client-repository[Registered Client Repository]
* xref:guides/how-to-redis.adoc#redis-authorization-service[Authorization Service]
* xref:guides/how-to-redis.adoc#redis-authorization-consent-service[Authorization Consent Service]
[[redis-registered-client-repository]]
=== Registered Client Repository
The following listing shows the `RedisRegisteredClientRepository`, which uses an xref:guides/how-to-redis.adoc#registered-client-repository[`OAuth2RegisteredClientRepository`] for persisting an xref:guides/how-to-redis.adoc#registered-client-entity[`OAuth2RegisteredClient`] and maps to and from the xref:core-model-components.adoc#registered-client[`RegisteredClient`] domain object, using the `ModelMapper` utility class.
.RedisRegisteredClientRepository
[source,java]
----
include::{examples-dir}/main/java/sample/redis/service/RedisRegisteredClientRepository.java[]
----
[[redis-authorization-service]]
=== Authorization Service
The following listing shows the `RedisOAuth2AuthorizationService`, which uses an xref:guides/how-to-redis.adoc#authorization-grant-repository[`OAuth2AuthorizationGrantAuthorizationRepository`] for persisting an xref:guides/how-to-redis.adoc#authorization-grant-entity[`OAuth2AuthorizationGrantAuthorization`] and maps to and from the xref:core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object, using the `ModelMapper` utility class.
.RedisOAuth2AuthorizationService
[source,java]
----
include::{examples-dir}/main/java/sample/redis/service/RedisOAuth2AuthorizationService.java[]
----
[[redis-authorization-consent-service]]
=== Authorization Consent Service
The following listing shows the `RedisOAuth2AuthorizationConsentService`, which uses an xref:guides/how-to-redis.adoc#authorization-consent-repository[`OAuth2UserConsentRepository`] for persisting an xref:guides/how-to-redis.adoc#authorization-consent-entity[`OAuth2UserConsent`] and maps to and from the xref:core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object, using the `ModelMapper` utility class.
.RedisOAuth2AuthorizationConsentService
[source,java]
----
include::{examples-dir}/main/java/sample/redis/service/RedisOAuth2AuthorizationConsentService.java[]
----
[[configure-core-services]]
== Configure core services
The following example shows how to configure the core services:
.RedisConfig
[source,java]
----
include::{examples-dir}/main/java/sample/redis/config/RedisConfig.java[]
----
<1> Activate the Spring Data Redis repositories under the `sample.redis.repository` base package.
<2> Use the https://docs.spring.io/spring-data/redis/reference/redis/drivers.html#redis:connectors:jedis[Jedis] Connector.
<3> Register the custom ``Converter``'s that perform the Object-to-Hash conversion before persisting to Redis.
<4> Register the `RedisRegisteredClientRepository` with the activated `OAuth2RegisteredClientRepository`.
<5> Register the `RedisOAuth2AuthorizationService` with the activated `OAuth2AuthorizationGrantAuthorizationRepository`.
<6> Register the `RedisOAuth2AuthorizationConsentService` with the activated `OAuth2UserConsentRepository`.

View File

@@ -12,6 +12,10 @@ java {
sourceCompatibility = JavaVersion.VERSION_17
}
compileJava {
options.compilerArgs << '-parameters'
}
antora {
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
@@ -57,6 +61,10 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation ("org.springframework.boot:spring-boot-starter-data-redis") {
exclude group: "io.lettuce", module: "lettuce-core"
}
implementation "redis.clients:jedis"
implementation "org.springframework:spring-webflux"
implementation project(":spring-security-oauth2-authorization-server")
runtimeOnly "com.h2database:h2"

View File

@@ -0,0 +1,85 @@
/*
* 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.redis.config;
import java.util.Arrays;
import sample.redis.convert.BytesToClaimsHolderConverter;
import sample.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
import sample.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
import sample.redis.convert.ClaimsHolderToBytesConverter;
import sample.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
import sample.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
import sample.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
import sample.redis.repository.OAuth2RegisteredClientRepository;
import sample.redis.repository.OAuth2UserConsentRepository;
import sample.redis.service.RedisOAuth2AuthorizationConsentService;
import sample.redis.service.RedisOAuth2AuthorizationService;
import sample.redis.service.RedisRegisteredClientRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@EnableRedisRepositories("sample.redis.repository") // <1>
@Configuration(proxyBeanMethods = false)
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory(); // <2>
}
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public RedisCustomConversions redisCustomConversions() { // <3>
return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
new BytesToUsernamePasswordAuthenticationTokenConverter(),
new OAuth2AuthorizationRequestToBytesConverter(), new BytesToOAuth2AuthorizationRequestConverter(),
new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
}
@Bean
public RedisRegisteredClientRepository registeredClientRepository(
OAuth2RegisteredClientRepository registeredClientRepository) {
return new RedisRegisteredClientRepository(registeredClientRepository); // <4>
}
@Bean
public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
return new RedisOAuth2AuthorizationService(registeredClientRepository,
authorizationGrantAuthorizationRepository); // <5>
}
@Bean
public RedisOAuth2AuthorizationConsentService authorizationConsentService(
OAuth2UserConsentRepository userConsentRepository) {
return new RedisOAuth2AuthorizationConsentService(userConsentRepository); // <6>
}
}

View File

@@ -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.redis.convert;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
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.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
@ReadingConverter
public class BytesToClaimsHolderConverter
implements Converter<byte[], OAuth2AuthorizationGrantAuthorization.ClaimsHolder> {
private final Jackson2JsonRedisSerializer<OAuth2AuthorizationGrantAuthorization.ClaimsHolder> 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);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.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;
@ReadingConverter
public class BytesToOAuth2AuthorizationRequestConverter implements Converter<byte[], OAuth2AuthorizationRequest> {
private final Jackson2JsonRedisSerializer<OAuth2AuthorizationRequest> 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);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.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;
@ReadingConverter
public class BytesToUsernamePasswordAuthenticationTokenConverter
implements Converter<byte[], UsernamePasswordAuthenticationToken> {
private final Jackson2JsonRedisSerializer<UsernamePasswordAuthenticationToken> 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);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.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;
@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<String, Object> claims) {
}
}

View File

@@ -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.redis.convert;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
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.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
@WritingConverter
public class ClaimsHolderToBytesConverter
implements Converter<OAuth2AuthorizationGrantAuthorization.ClaimsHolder, byte[]> {
private final Jackson2JsonRedisSerializer<OAuth2AuthorizationGrantAuthorization.ClaimsHolder> 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);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.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;
@WritingConverter
public class OAuth2AuthorizationRequestToBytesConverter implements Converter<OAuth2AuthorizationRequest, byte[]> {
private final Jackson2JsonRedisSerializer<OAuth2AuthorizationRequest> 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);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.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;
@WritingConverter
public class UsernamePasswordAuthenticationTokenToBytesConverter
implements Converter<UsernamePasswordAuthenticationToken, byte[]> {
private final Jackson2JsonRedisSerializer<UsernamePasswordAuthenticationToken> 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);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.redis.entity;
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;
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
// @fold:on
public OAuth2AuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> 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);
}
}
// @fold:off
}

View File

@@ -0,0 +1,176 @@
/*
* 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.redis.entity;
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;
@RedisHash("oauth2_authorization")
public abstract class OAuth2AuthorizationGrantAuthorization {
@Id
private final String id;
private final String registeredClientId;
private final String principalName;
private final Set<String> authorizedScopes;
private final AccessToken accessToken;
private final RefreshToken refreshToken;
// @fold:on
protected OAuth2AuthorizationGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> 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<String> 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<String, Object> claims;
public ClaimsHolder(Map<String, Object> claims) {
this.claims = claims;
}
public Map<String, Object> getClaims() {
return this.claims;
}
}
public static class AccessToken extends AbstractToken {
private final OAuth2AccessToken.TokenType tokenType;
private final Set<String> scopes;
private final OAuth2TokenFormat tokenFormat;
private final ClaimsHolder claims;
public AccessToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated,
OAuth2AccessToken.TokenType tokenType, Set<String> 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<String> 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);
}
}
// @fold:off
}

View File

@@ -0,0 +1,29 @@
/*
* 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.redis.entity;
import java.util.Set;
public class OAuth2ClientCredentialsGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
// @fold:on
public OAuth2ClientCredentialsGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
}
// @fold:off
}

View File

@@ -0,0 +1,87 @@
/*
* 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.redis.entity;
import java.security.Principal;
import java.time.Instant;
import java.util.Set;
import org.springframework.data.redis.core.index.Indexed;
public class OAuth2DeviceCodeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
private final Principal principal;
private final DeviceCode deviceCode;
private final UserCode userCode;
private final Set<String> requestedScopes;
@Indexed
private final String deviceState; // Used to correlate the request during the
// authorization consent flow
// @fold:on
public OAuth2DeviceCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal,
DeviceCode deviceCode, UserCode userCode, Set<String> 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<String> 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);
}
}
// @fold:off
}

View File

@@ -0,0 +1,246 @@
/*
* 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.redis.entity;
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;
@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<ClientAuthenticationMethod> clientAuthenticationMethods;
private final Set<AuthorizationGrantType> authorizationGrantTypes;
private final Set<String> redirectUris;
private final Set<String> postLogoutRedirectUris;
private final Set<String> scopes;
private final ClientSettings clientSettings;
private final TokenSettings tokenSettings;
// @fold:on
public OAuth2RegisteredClient(String id, String clientId, Instant clientIdIssuedAt, String clientSecret,
Instant clientSecretExpiresAt, String clientName,
Set<ClientAuthenticationMethod> clientAuthenticationMethods,
Set<AuthorizationGrantType> authorizationGrantTypes, Set<String> redirectUris,
Set<String> postLogoutRedirectUris, Set<String> 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<ClientAuthenticationMethod> getClientAuthenticationMethods() {
return this.clientAuthenticationMethods;
}
public Set<AuthorizationGrantType> getAuthorizationGrantTypes() {
return this.authorizationGrantTypes;
}
public Set<String> getRedirectUris() {
return this.redirectUris;
}
public Set<String> getPostLogoutRedirectUris() {
return this.postLogoutRedirectUris;
}
public Set<String> 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;
}
}
// @fold:off
}

View File

@@ -0,0 +1,29 @@
/*
* 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.redis.entity;
import java.util.Set;
public class OAuth2TokenExchangeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
// @fold:on
public OAuth2TokenExchangeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
}
// @fold:off
}

View File

@@ -0,0 +1,65 @@
/*
* 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.redis.entity;
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;
@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<GrantedAuthority> authorities;
// @fold:on
public OAuth2UserConsent(String id, String registeredClientId, String principalName,
Set<GrantedAuthority> 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<GrantedAuthority> getAuthorities() {
return this.authorities;
}
// @fold:off
}

View File

@@ -0,0 +1,59 @@
/*
* 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.redis.entity;
import java.security.Principal;
import java.time.Instant;
import java.util.Set;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
public class OidcAuthorizationCodeGrantAuthorization extends OAuth2AuthorizationCodeGrantAuthorization {
private final IdToken idToken;
// @fold:on
public OidcAuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> 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;
}
}
// @fold:off
}

View File

@@ -0,0 +1,50 @@
/*
* 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.redis.repository;
import sample.redis.entity.OAuth2AuthorizationCodeGrantAuthorization;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
import sample.redis.entity.OAuth2DeviceCodeGrantAuthorization;
import sample.redis.entity.OidcAuthorizationCodeGrantAuthorization;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AuthorizationGrantAuthorizationRepository
extends CrudRepository<OAuth2AuthorizationGrantAuthorization, String> {
<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByState(String token);
<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByAuthorizationCode_TokenValue(String token);
<T extends OAuth2AuthorizationGrantAuthorization> T findByAccessToken_TokenValue(String token);
<T extends OAuth2AuthorizationGrantAuthorization> T findByRefreshToken_TokenValue(String token);
<T extends OidcAuthorizationCodeGrantAuthorization> T findByIdToken_TokenValue(String token);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceState(String token);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceCode_TokenValue(String token);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByUserCode_TokenValue(String token);
<T extends OAuth2AuthorizationGrantAuthorization> T findByStateOrAuthorizationCode_TokenValueOrAccessToken_TokenValueOrRefreshToken_TokenValueOrIdToken_TokenValueOrDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue(
String state, String authorizationCode, String accessToken, String refreshToken, String idToken,
String deviceState, String deviceCode, String userCode);
}

View File

@@ -0,0 +1,28 @@
/*
* 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.redis.repository;
import sample.redis.entity.OAuth2RegisteredClient;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RegisteredClientRepository extends CrudRepository<OAuth2RegisteredClient, String> {
OAuth2RegisteredClient findByClientId(String clientId);
}

View File

@@ -0,0 +1,30 @@
/*
* 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.redis.repository;
import sample.redis.entity.OAuth2UserConsent;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2UserConsentRepository extends CrudRepository<OAuth2UserConsent, String> {
OAuth2UserConsent findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
}

View File

@@ -0,0 +1,498 @@
/*
* 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.redis.service;
import java.security.Principal;
import sample.redis.entity.OAuth2AuthorizationCodeGrantAuthorization;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
import sample.redis.entity.OAuth2ClientCredentialsGrantAuthorization;
import sample.redis.entity.OAuth2DeviceCodeGrantAuthorization;
import sample.redis.entity.OAuth2RegisteredClient;
import sample.redis.entity.OAuth2TokenExchangeGrantAuthorization;
import sample.redis.entity.OAuth2UserConsent;
import sample.redis.entity.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;
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> 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> 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> 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> 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> 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> 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()));
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.redis.service;
import sample.redis.entity.OAuth2UserConsent;
import sample.redis.repository.OAuth2UserConsentRepository;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.util.Assert;
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;
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.redis.service;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
import sample.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
import org.springframework.lang.Nullable;
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;
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();
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.redis.service;
import sample.redis.entity.OAuth2RegisteredClient;
import sample.redis.repository.OAuth2RegisteredClientRepository;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
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;
}
}