From e1054cda23fdff7a151d1d3eb2a6c16d15d0d2c4 Mon Sep 17 00:00:00 2001
From: Omkar A <omkara.adg@gmail.com>
Date: Fri, 21 Mar 2025 18:47:32 +0530
Subject: [PATCH 1/2] -- initial commit

---
 pom.xml                                       | 376 +++++++-------
 redis-authorization-server/.gitignore         |   3 +
 redis-authorization-server/pom.xml            |  23 +
 .../redis-auth-client/.gitignore              |   5 +
 .../redis-auth-client/pom.xml                 |  62 +++
 .../client/RedisAuthClientApplication.java    |  12 +
 .../oauth/client/config/SecurityConfig.java   |  21 +
 .../oauth/client/config/WebClientConfig.java  |  39 ++
 .../oauth/client/web/ArticlesController.java  |  29 ++
 .../src/main/resources/application.yml        |  35 ++
 .../redis-oauth-server/.gitignore             |   5 +
 .../redis-oauth-server/pom.xml                |  67 +++
 ...isOAuth2AuhorizationServerApplication.java |  12 +
 .../baeldung/redis/config/RedisConfig.java    |  96 ++++
 .../redis/config/RegisteredClients.java       |  34 ++
 .../baeldung/redis/config/SecurityConfig.java |  63 +++
 .../convert/BytesToClaimsHolderConverter.java |  29 ++
 ...ToOAuth2AuthorizationRequestConverter.java |  29 ++
 ...ePasswordAuthenticationTokenConverter.java |  28 +
 .../redis/convert/ClaimsHolderMixin.java      |  20 +
 .../convert/ClaimsHolderToBytesConverter.java |  31 ++
 ...2AuthorizationRequestToBytesConverter.java |  30 ++
 ...rdAuthenticationTokenToBytesConverter.java |  27 +
 ...h2AuthorizationCodeGrantAuthorization.java |  55 ++
 ...OAuth2AuthorizationGrantAuthorization.java | 158 ++++++
 ...h2ClientCredentialsGrantAuthorization.java |  12 +
 .../OAuth2DeviceCodeGrantAuthorization.java   |  68 +++
 .../redis/entity/OAuth2RegisteredClient.java  | 226 ++++++++
 ...OAuth2TokenExchangeGrantAuthorization.java |  12 +
 .../redis/entity/OAuth2UserConsent.java       |  47 ++
 ...dcAuthorizationCodeGrantAuthorization.java |  40 ++
 ...orizationGrantAuthorizationRepository.java |  37 ++
 .../OAuth2RegisteredClientRepository.java     |  13 +
 .../OAuth2UserConsentRepository.java          |  15 +
 .../baeldung/redis/service/ModelMapper.java   | 483 ++++++++++++++++++
 ...edisOAuth2AuthorizationConsentService.java |  43 ++
 .../RedisOAuth2AuthorizationService.java      |  98 ++++
 .../RedisRegisteredClientRepository.java      |  44 ++
 .../src/main/resources/application.yml        |  16 +
 .../redis-resource-server/.gitignore          |   4 +
 .../redis-resource-server/pom.xml             |  62 +++
 .../resource/ResourceServerApplication.java   |  12 +
 .../resource/config/ResourceServerConfig.java |  22 +
 .../resource/web/ArticlesController.java      |  13 +
 .../src/main/resources/application.yml        |  18 +
 45 files changed, 2388 insertions(+), 186 deletions(-)
 create mode 100644 redis-authorization-server/.gitignore
 create mode 100644 redis-authorization-server/pom.xml
 create mode 100644 redis-authorization-server/redis-auth-client/.gitignore
 create mode 100644 redis-authorization-server/redis-auth-client/pom.xml
 create mode 100644 redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/RedisAuthClientApplication.java
 create mode 100644 redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/SecurityConfig.java
 create mode 100644 redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/WebClientConfig.java
 create mode 100644 redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/web/ArticlesController.java
 create mode 100644 redis-authorization-server/redis-auth-client/src/main/resources/application.yml
 create mode 100644 redis-authorization-server/redis-oauth-server/.gitignore
 create mode 100644 redis-authorization-server/redis-oauth-server/pom.xml
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/RedisOAuth2AuhorizationServerApplication.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RedisConfig.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RegisteredClients.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/SecurityConfig.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToClaimsHolderConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderMixin.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderToBytesConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2DeviceCodeGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2RegisteredClient.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2TokenExchangeGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2UserConsent.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OidcAuthorizationCodeGrantAuthorization.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2RegisteredClientRepository.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2UserConsentRepository.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/ModelMapper.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationConsentService.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationService.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisRegisteredClientRepository.java
 create mode 100644 redis-authorization-server/redis-oauth-server/src/main/resources/application.yml
 create mode 100644 redis-authorization-server/redis-resource-server/.gitignore
 create mode 100644 redis-authorization-server/redis-resource-server/pom.xml
 create mode 100644 redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/ResourceServerApplication.java
 create mode 100644 redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/config/ResourceServerConfig.java
 create mode 100644 redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/web/ArticlesController.java
 create mode 100644 redis-authorization-server/redis-resource-server/src/main/resources/application.yml

diff --git a/pom.xml b/pom.xml
index b66e40530..a535849f4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,200 +1,204 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<groupId>com.baeldung</groupId>
-	<artifactId>spring-security-oauth</artifactId>
-	<version>1.0.0-SNAPSHOT</version>
-
-	<name>spring-security-oauth</name>
-	<packaging>pom</packaging>
-
-	<parent>
-		<groupId>org.springframework.boot</groupId>
-		<artifactId>spring-boot-starter-parent</artifactId>
-		<version>2.1.17.RELEASE</version>
-		<relativePath></relativePath>
-	</parent>
-
-	<build>
-		<finalName>spring-security-oauth</finalName>
-		<pluginManagement>
-			<plugins>
-
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-compiler-plugin</artifactId>
-					<version>${maven-compiler-plugin.version}</version>
-					<configuration>
-						<source>1.8</source>
-						<target>1.8</target>
-					</configuration>
-				</plugin>
-
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-war-plugin</artifactId>
-					<version>${maven-war-plugin.version}</version>
-					<configuration>
-						<failOnMissingWebXml>false</failOnMissingWebXml>
-					</configuration>
-				</plugin>
-
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-surefire-plugin</artifactId>
-					<version>${maven-surefire-plugin.version}</version>
-					<configuration>
-						<testFailureIgnore>true</testFailureIgnore>
-						<excludes>
-							<exclude>**/*IntegrationTest.java</exclude>
-							<exclude>**/*LiveTest.java</exclude>
-							<exclude>**/*MvcTest.java</exclude>
-						</excludes>
-					</configuration>
-				</plugin>
-
-			</plugins>
-		</pluginManagement>
-	</build>
-
-	<properties>
-		<oauth.version>2.3.5.RELEASE</oauth.version>
-		<jwt.version>1.0.10.RELEASE</jwt.version>
-		<swagger.version>2.9.2</swagger.version>
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.baeldung</groupId>
+    <artifactId>spring-security-oauth</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>spring-security-oauth</name>
+    <packaging>pom</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.17.RELEASE</version>
+        <relativePath></relativePath>
+    </parent>
+
+    <build>
+        <finalName>spring-security-oauth</finalName>
+        <pluginManagement>
+            <plugins>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>${maven-compiler-plugin.version}</version>
+                    <configuration>
+                        <source>1.8</source>
+                        <target>1.8</target>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-war-plugin</artifactId>
+                    <version>${maven-war-plugin.version}</version>
+                    <configuration>
+                        <failOnMissingWebXml>false</failOnMissingWebXml>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                    <configuration>
+                        <testFailureIgnore>true</testFailureIgnore>
+                        <excludes>
+                            <exclude>**/*IntegrationTest.java</exclude>
+                            <exclude>**/*LiveTest.java</exclude>
+                            <exclude>**/*MvcTest.java</exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <properties>
+        <oauth.version>2.3.5.RELEASE</oauth.version>
+        <jwt.version>1.0.10.RELEASE</jwt.version>
+        <swagger.version>2.9.2</swagger.version>
         <netflix-zuul.version>2.1.1.RELEASE</netflix-zuul.version>
         <oauth-autoconfig.version>2.1.3.RELEASE</oauth-autoconfig.version>
 
-		<!-- util -->
-		<commons-io.version>2.6</commons-io.version>
+        <!-- util -->
+        <commons-io.version>2.6</commons-io.version>
 
-		<!-- Maven plugins -->
-		<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
-		<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
-	</properties>
+        <!-- Maven plugins -->
+        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
+        <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
+    </properties>
 
 
-	<modules>
-		<module>oauth-legacy/oauth-resource-server-legacy-2</module>
-		<module>oauth-legacy/oauth-resource-server-legacy-1</module>
-		<module>oauth-rest/oauth-resource-server</module>
-		<module>oauth-jwt/jwt-resource-server</module>
-		<module>oauth-sso/sso-resource-server</module>
-		<module>oauth-resource-server/resource-server-jwt</module>
-		<module>oauth-resource-server/resource-server-opaque</module>
-		<module>oauth-authorization-server/resource-server</module>
+    <modules>
+        <module>oauth-legacy/oauth-resource-server-legacy-2</module>
+        <module>oauth-legacy/oauth-resource-server-legacy-1</module>
+        <module>oauth-rest/oauth-resource-server</module>
+        <module>oauth-jwt/jwt-resource-server</module>
+        <module>oauth-sso/sso-resource-server</module>
+        <module>oauth-resource-server/resource-server-jwt</module>
+        <module>oauth-resource-server/resource-server-opaque</module>
+        <module>oauth-authorization-server/resource-server</module>
 
-		<module>oauth-legacy/oauth-ui-implicit-angularjs-legacy</module>
-		<module>oauth-legacy/oauth-ui-password-angularjs-legacy</module>
+        <module>oauth-legacy/oauth-ui-implicit-angularjs-legacy</module>
+        <module>oauth-legacy/oauth-ui-password-angularjs-legacy</module>
        
         <module>clients-SPA-legacy/clients-js-only-react-legacy</module>
         <module>clients-SPA-legacy/oauth-resource-server-auth0-legacy</module>
 
-		<module>oauth-rest/oauth-authorization-server</module>
-		<module>oauth-legacy/oauth-authorization-server-legacy</module>
-		<module>oauth-jwt/jwt-auth-server</module>
-		<module>oauth-sso/sso-authorization-server</module>
-		<module>oauth-legacy/oauth-zuul-gateway</module>
+        <module>oauth-rest/oauth-authorization-server</module>
+        <module>oauth-legacy/oauth-authorization-server-legacy</module>
+        <module>oauth-jwt/jwt-auth-server</module>
+        <module>oauth-sso/sso-authorization-server</module>
+        <module>oauth-legacy/oauth-zuul-gateway</module>
         <module>oauth-legacy/oauth-microservices</module>
-		<module>oauth-resource-server/authorization-server</module>
-		<module>oauth-authorization-server/spring-authorization-server</module>
-		
-		<module>oauth-jws-jwk-legacy\oauth-authorization-server-jws-jwk-legacy</module>
-		<module>oauth-jws-jwk-legacy\oauth-resource-server-jws-jwk-legacy</module>
-		
-		<module>oauth-sso/sso-client-app-1</module>
-		<module>oauth-sso/sso-client-app-2</module>
-		<module>oauth-authorization-server/client-server</module>
-
-		<!-- commented out because these don't build on Jenkins -->
-		<!-- <module>oauth-legacy/oauth-ui-implicit-angular</module> <module>oauth-legacy/oauth-ui-password-angular</module> -->
-	</modules>
-
-	<profiles>
-		<profile>
-			<id>integration</id>
-			<build>
-				<plugins>
-					<plugin>
-						<groupId>org.apache.maven.plugins</groupId>
-						<artifactId>maven-surefire-plugin</artifactId>
-						<executions>
-							<execution>
-								<phase>integration-test</phase>
-								<goals>
-									<goal>test</goal>
-								</goals>
-								<configuration>
-									<excludes>
-										<exclude>**/*LiveTest.java</exclude>
-									</excludes>
-									<includes>
-										<include>**/*IntegrationTest.java</include>
-									</includes>
-								</configuration>
-							</execution>
-						</executions>
-						<configuration>
-							<systemPropertyVariables>
-								<test.mime>json</test.mime>
-							</systemPropertyVariables>
-						</configuration>
-					</plugin>
-				</plugins>
-			</build>
-		</profile>
-
-		<profile>
-			<id>live</id>
-			<build>
-				<plugins>
-					<plugin>
-						<groupId>org.codehaus.cargo</groupId>
-						<artifactId>cargo-maven2-plugin</artifactId>
-						<executions>
-							<execution>
-								<id>start-server</id>
-								<phase>pre-integration-test</phase>
-								<goals>
-									<goal>start</goal>
-								</goals>
-							</execution>
-							<execution>
-								<id>stop-server</id>
-								<phase>post-integration-test</phase>
-								<goals>
-									<goal>stop</goal>
-								</goals>
-							</execution>
-						</executions>
-					</plugin>
-
-					<plugin>
-						<groupId>org.apache.maven.plugins</groupId>
-						<artifactId>maven-surefire-plugin</artifactId>
-						<executions>
-							<execution>
-								<phase>integration-test</phase>
-								<goals>
-									<goal>test</goal>
-								</goals>
-								<configuration>
-									<excludes>
-										<exclude>none</exclude>
-									</excludes>
-									<includes>
-										<include>**/*LiveTest.java</include>
-									</includes>
-									<systemPropertyVariables>
-										<webTarget>cargo</webTarget>
-									</systemPropertyVariables>
-								</configuration>
-							</execution>
-						</executions>
-					</plugin>
-
-				</plugins>
-			</build>
-		</profile>
-	</profiles>
+        <module>oauth-resource-server/authorization-server</module>
+        <module>oauth-authorization-server/spring-authorization-server</module>
+        
+        <module>oauth-jws-jwk-legacy\oauth-authorization-server-jws-jwk-legacy</module>
+        <module>oauth-jws-jwk-legacy\oauth-resource-server-jws-jwk-legacy</module>
+        
+        <module>oauth-sso/sso-client-app-1</module>
+        <module>oauth-sso/sso-client-app-2</module>
+        <module>oauth-authorization-server/client-server</module>
+
+        <module>redis-authorization-server/redis-auth-client</module>
+        <module>redis-authorization-server/redis-oauth-server</module>
+        <module>redis-authorization-server/redis-resource-server</module>
+
+        <!-- commented out because these don't build on Jenkins -->
+        <!-- <module>oauth-legacy/oauth-ui-implicit-angular</module> <module>oauth-legacy/oauth-ui-password-angular</module> -->
+    </modules>
+
+    <profiles>
+        <profile>
+            <id>integration</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>integration-test</phase>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <configuration>
+                                    <excludes>
+                                        <exclude>**/*LiveTest.java</exclude>
+                                    </excludes>
+                                    <includes>
+                                        <include>**/*IntegrationTest.java</include>
+                                    </includes>
+                                </configuration>
+                            </execution>
+                        </executions>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <test.mime>json</test.mime>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>live</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.cargo</groupId>
+                        <artifactId>cargo-maven2-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>start-server</id>
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>start</goal>
+                                </goals>
+                            </execution>
+                            <execution>
+                                <id>stop-server</id>
+                                <phase>post-integration-test</phase>
+                                <goals>
+                                    <goal>stop</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>integration-test</phase>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <configuration>
+                                    <excludes>
+                                        <exclude>none</exclude>
+                                    </excludes>
+                                    <includes>
+                                        <include>**/*LiveTest.java</include>
+                                    </includes>
+                                    <systemPropertyVariables>
+                                        <webTarget>cargo</webTarget>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git a/redis-authorization-server/.gitignore b/redis-authorization-server/.gitignore
new file mode 100644
index 000000000..fbdf7ebb0
--- /dev/null
+++ b/redis-authorization-server/.gitignore
@@ -0,0 +1,3 @@
+/.classpath
+/.project
+/.settings/
diff --git a/redis-authorization-server/pom.xml b/redis-authorization-server/pom.xml
new file mode 100644
index 000000000..0eb53b96a
--- /dev/null
+++ b/redis-authorization-server/pom.xml
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <description>redis-authorization-server</description>
+
+    <groupId>com.baeldung</groupId>
+    <artifactId>redis-authorization-server</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+      	<version>3.4.3</version>
+        <relativePath /> <!-- lookup parent from repository -->
+    </parent>
+
+    <modules>
+    	<module>redis-oauth-server</module>
+    	<module>redis-auth-client</module>
+    	<module>redis-resource-server</module>
+    </modules>
+</project>
diff --git a/redis-authorization-server/redis-auth-client/.gitignore b/redis-authorization-server/redis-auth-client/.gitignore
new file mode 100644
index 000000000..d1451cc0c
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/.gitignore
@@ -0,0 +1,5 @@
+/target/
+/.classpath
+/.factorypath
+/.project
+/.settings/
diff --git a/redis-authorization-server/redis-auth-client/pom.xml b/redis-authorization-server/redis-auth-client/pom.xml
new file mode 100644
index 000000000..c0fbbf6aa
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/pom.xml
@@ -0,0 +1,62 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.baeldung</groupId>
+    <artifactId>auth-client</artifactId>
+    <name>auth-client</name>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+       	<version>3.4.3</version>
+        <relativePath />
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*LiveTest.java</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <developers>
+        <developer>
+            <email>eugen@baeldung.com</email>
+            <name>Eugen Paraschiv</name>
+            <url>https://github.com/eugenp</url>
+            <id>eugenp</id>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>17</java.version>
+    </properties>
+
+</project>
diff --git a/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/RedisAuthClientApplication.java b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/RedisAuthClientApplication.java
new file mode 100644
index 000000000..8a7f6fd28
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/RedisAuthClientApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.oauth.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RedisAuthClientApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(RedisAuthClientApplication.class, args);
+    }
+}
diff --git a/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/SecurityConfig.java b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/SecurityConfig.java
new file mode 100644
index 000000000..3177ab3b9
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/SecurityConfig.java
@@ -0,0 +1,21 @@
+package com.baeldung.oauth.client.config;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+@EnableWebSecurity
+public class SecurityConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest()
+            .authenticated())
+            .oauth2Login(oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
+            .oauth2Client(withDefaults());
+        return http.build();
+    }
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/WebClientConfig.java b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/WebClientConfig.java
new file mode 100644
index 000000000..b018bff84
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/config/WebClientConfig.java
@@ -0,0 +1,39 @@
+package com.baeldung.oauth.client.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Configuration
+public class WebClientConfig {
+
+    @Bean
+    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
+        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
+        return WebClient.builder()
+            .apply(oauth2Client.oauth2Configuration())
+            .build();
+    }
+
+    @Bean
+    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
+        OAuth2AuthorizedClientRepository authorizedClientRepository) {
+
+        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
+            .authorizationCode()
+            .refreshToken()
+            .build();
+        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
+            authorizedClientRepository);
+        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+        return authorizedClientManager;
+    }
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/web/ArticlesController.java b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/web/ArticlesController.java
new file mode 100644
index 000000000..0c7e6189e
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/src/main/java/com/baeldung/oauth/client/web/ArticlesController.java
@@ -0,0 +1,29 @@
+package com.baeldung.oauth.client.web;
+
+import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient;
+
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@RestController
+public class ArticlesController {
+
+    public ArticlesController(WebClient webClient) {
+        this.webClient = webClient;
+    }
+
+    private WebClient webClient;
+
+    @GetMapping(value = "/articles")
+    public String[] getArticles(@RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient) {
+        return this.webClient.get()
+            .uri("http://127.0.0.1:8090/articles")
+            .attributes(oauth2AuthorizedClient(authorizedClient))
+            .retrieve()
+            .bodyToMono(String[].class)
+            .block();
+    }
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-auth-client/src/main/resources/application.yml b/redis-authorization-server/redis-auth-client/src/main/resources/application.yml
new file mode 100644
index 000000000..9f9d6a3f3
--- /dev/null
+++ b/redis-authorization-server/redis-auth-client/src/main/resources/application.yml
@@ -0,0 +1,35 @@
+server:
+  port: 8085
+
+logging:
+  level:
+    root: INFO
+    org.springframework.web: INFO
+    org.springframework.security: INFO
+    org.springframework.security.oauth2: INFO
+    org.springframework.boot: INFO
+
+spring:
+  security:
+    oauth2:
+      client:
+        registration:
+          articles-client-oidc:
+            provider: spring
+            client-id: articles-client
+            client-secret: secret
+            authorization-grant-type: authorization_code
+            redirect-uri: "http://127.0.0.1:8085/login/oauth2/code/{registrationId}"
+            scope: openid
+            client-name: articles-client-oidc
+          articles-client-authorization-code:
+            provider: spring
+            client-id: articles-client
+            client-secret: secret
+            authorization-grant-type: authorization_code
+            redirect-uri: "http://127.0.0.1:8085/authorized"
+            scope: articles.read
+            client-name: articles-client-authorization-code
+        provider:
+          spring:
+            issuer-uri: http://localhost:9000
\ No newline at end of file
diff --git a/redis-authorization-server/redis-oauth-server/.gitignore b/redis-authorization-server/redis-oauth-server/.gitignore
new file mode 100644
index 000000000..d1451cc0c
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/.gitignore
@@ -0,0 +1,5 @@
+/target/
+/.classpath
+/.factorypath
+/.project
+/.settings/
diff --git a/redis-authorization-server/redis-oauth-server/pom.xml b/redis-authorization-server/redis-oauth-server/pom.xml
new file mode 100644
index 000000000..069909c22
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/pom.xml
@@ -0,0 +1,67 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.baeldung</groupId>
+    <artifactId>redis-oauth-server</artifactId>
+    <name>redis-oauth-server</name>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.3</version>
+        <relativePath />
+    </parent>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*LiveTest.java</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <developers>
+        <developer>
+            <email>eugen@baeldung.com</email>
+            <name>Eugen Paraschiv</name>
+            <url>https://github.com/eugenp</url>
+            <id>eugenp</id>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>17</java.version>
+        <spring-boot-starter.version>3.4.3</spring-boot-starter.version>
+    </properties>
+
+</project>
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/RedisOAuth2AuhorizationServerApplication.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/RedisOAuth2AuhorizationServerApplication.java
new file mode 100644
index 000000000..eae5de4f8
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/RedisOAuth2AuhorizationServerApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.redis;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RedisOAuth2AuhorizationServerApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(RedisOAuth2AuhorizationServerApplication.class, args);
+    }
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RedisConfig.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RedisConfig.java
new file mode 100644
index 000000000..8811b663d
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RedisConfig.java
@@ -0,0 +1,96 @@
+package com.baeldung.redis.config;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+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;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import com.baeldung.redis.convert.BytesToClaimsHolderConverter;
+import com.baeldung.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
+import com.baeldung.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
+import com.baeldung.redis.convert.ClaimsHolderToBytesConverter;
+import com.baeldung.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
+import com.baeldung.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
+import com.baeldung.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
+import com.baeldung.redis.repository.OAuth2RegisteredClientRepository;
+import com.baeldung.redis.repository.OAuth2UserConsentRepository;
+import com.baeldung.redis.service.RedisOAuth2AuthorizationConsentService;
+import com.baeldung.redis.service.RedisOAuth2AuthorizationService;
+import com.baeldung.redis.service.RedisRegisteredClientRepository;
+
+import jakarta.annotation.PreDestroy;
+
+@EnableRedisRepositories("com.baeldung.redis.repository")
+@Configuration(proxyBeanMethods = false)
+public class RedisConfig {
+
+    private static GenericContainer<?> redis;
+
+    static {
+        redis = new GenericContainer<>(DockerImageName.parse("redis:5.0.3-alpine")).withExposedPorts(6379);
+        redis.start();
+        System.setProperty("spring.redis.host", redis.getHost());
+        System.setProperty("spring.redis.port", redis.getMappedPort(6379)
+            .toString());
+    }
+
+    @PreDestroy
+    public void preDestroy() throws IOException {
+        redis.stop();
+    }
+
+    @Bean
+    @Order(1)
+    public RedisConnectionFactory redisConnectionFactory() {
+        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redis.getHost(), redis.getMappedPort(6379));
+        return new LettuceConnectionFactory(config);
+    }
+
+    @Bean
+    @Order(2)
+    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        return redisTemplate;
+    }
+
+    @Bean
+    @Order(3)
+    public RedisCustomConversions redisCustomConversions() {
+        return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
+            new BytesToUsernamePasswordAuthenticationTokenConverter(), new OAuth2AuthorizationRequestToBytesConverter(),
+            new BytesToOAuth2AuthorizationRequestConverter(), new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
+    }
+
+    @Bean
+    @Order(4)
+    public RedisRegisteredClientRepository registeredClientRepository(OAuth2RegisteredClientRepository registeredClientRepository) {
+        RedisRegisteredClientRepository redisRegisteredClientRepository = new RedisRegisteredClientRepository(registeredClientRepository);
+        redisRegisteredClientRepository.save(RegisteredClients.messagingClient());
+        return redisRegisteredClientRepository;
+    }
+
+    @Bean
+    @Order(5)
+    public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
+        OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
+        return new RedisOAuth2AuthorizationService(registeredClientRepository, authorizationGrantAuthorizationRepository);
+    }
+
+    @Bean
+    @Order(6)
+    public RedisOAuth2AuthorizationConsentService authorizationConsentService(OAuth2UserConsentRepository userConsentRepository) {
+        return new RedisOAuth2AuthorizationConsentService(userConsentRepository);
+    }
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RegisteredClients.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RegisteredClients.java
new file mode 100644
index 000000000..5aa492026
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/RegisteredClients.java
@@ -0,0 +1,34 @@
+package com.baeldung.redis.config;
+
+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;
+
+public class RegisteredClients {
+
+    public static RegisteredClient messagingClient() {
+        return RegisteredClient.withId(UUID.randomUUID()
+            .toString())
+            .clientId("articles-client")
+            .clientSecret("{noop}secret")
+            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+            .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
+            .redirectUri("http://127.0.0.1:8085/authorized")
+            .redirectUri("http://127.0.0.1:8085/login/oauth2/code/articles-client-oidc")
+            .postLogoutRedirectUri("http://127.0.0.1:9000/login")
+            .scope(OidcScopes.OPENID)
+            .scope("articles.read")
+            .clientSettings(ClientSettings.builder()
+                .requireAuthorizationConsent(true)
+                .build())
+            .build();
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/SecurityConfig.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/SecurityConfig.java
new file mode 100644
index 000000000..f09fbf320
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/config/SecurityConfig.java
@@ -0,0 +1,63 @@
+/**
+ * 
+ */
+package com.baeldung.redis.config;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+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.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+
+@EnableWebSecurity
+@Configuration
+@EnableAutoConfiguration(exclude = { JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
+@ComponentScan
+public class SecurityConfig {
+
+    @Bean
+    @Order(1)
+    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
+        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
+            .oidc(Customizer.withDefaults());
+        return http.formLogin(Customizer.withDefaults())
+            .build();
+    }
+
+    @Bean
+    @Order(2)
+    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest()
+            .authenticated())
+            .formLogin(Customizer.withDefaults());
+        return http.build();
+    }
+
+    @Bean
+    UserDetailsService users() {
+        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+        UserDetails user = User.builder()
+            .username("admin")
+            .password("password")
+            .passwordEncoder(encoder::encode)
+            .roles("USER")
+            .build();
+        return new InMemoryUserDetailsManager(user);
+    }
+
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToClaimsHolderConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToClaimsHolderConverter.java
new file mode 100644
index 000000000..6f7fedeb0
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToClaimsHolderConverter.java
@@ -0,0 +1,29 @@
+package com.baeldung.redis.convert;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2AuthorizationGrantAuthorization;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java
new file mode 100644
index 000000000..d40b16221
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java
@@ -0,0 +1,29 @@
+package com.baeldung.redis.convert;
+
+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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java
new file mode 100644
index 000000000..70c1fdf97
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java
@@ -0,0 +1,28 @@
+
+package com.baeldung.redis.convert;
+
+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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderMixin.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderMixin.java
new file mode 100644
index 000000000..4720765f3
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderMixin.java
@@ -0,0 +1,20 @@
+package com.baeldung.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) {
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderToBytesConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderToBytesConverter.java
new file mode 100644
index 000000000..046218dd1
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/ClaimsHolderToBytesConverter.java
@@ -0,0 +1,31 @@
+
+package com.baeldung.redis.convert;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2AuthorizationGrantAuthorization;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java
new file mode 100644
index 000000000..70e420272
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java
@@ -0,0 +1,30 @@
+
+package com.baeldung.redis.convert;
+
+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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java
new file mode 100644
index 000000000..1e660528f
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java
@@ -0,0 +1,27 @@
+package com.baeldung.redis.convert;
+
+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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@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);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java
new file mode 100644
index 000000000..3275776cb
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationCodeGrantAuthorization.java
@@ -0,0 +1,55 @@
+package com.baeldung.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;
+
+    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);
+        }
+
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationGrantAuthorization.java
new file mode 100644
index 000000000..6aa199790
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2AuthorizationGrantAuthorization.java
@@ -0,0 +1,158 @@
+package com.baeldung.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;
+
+    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);
+        }
+
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java
new file mode 100644
index 000000000..65d5c4082
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2ClientCredentialsGrantAuthorization.java
@@ -0,0 +1,12 @@
+package com.baeldung.redis.entity;
+
+import java.util.Set;
+
+public class OAuth2ClientCredentialsGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
+
+    public OAuth2ClientCredentialsGrantAuthorization(String id, String registeredClientId, String principalName, Set<String> authorizedScopes,
+        AccessToken accessToken) {
+        super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2DeviceCodeGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2DeviceCodeGrantAuthorization.java
new file mode 100644
index 000000000..fa013aa75
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2DeviceCodeGrantAuthorization.java
@@ -0,0 +1,68 @@
+
+package com.baeldung.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;
+
+    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);
+        }
+
+    }
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2RegisteredClient.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2RegisteredClient.java
new file mode 100644
index 000000000..b532585f7
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2RegisteredClient.java
@@ -0,0 +1,226 @@
+
+package com.baeldung.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;
+
+    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;
+        }
+
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2TokenExchangeGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2TokenExchangeGrantAuthorization.java
new file mode 100644
index 000000000..9a0549194
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2TokenExchangeGrantAuthorization.java
@@ -0,0 +1,12 @@
+package com.baeldung.redis.entity;
+
+import java.util.Set;
+
+public class OAuth2TokenExchangeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
+
+    public OAuth2TokenExchangeGrantAuthorization(String id, String registeredClientId, String principalName, Set<String> authorizedScopes,
+        AccessToken accessToken) {
+        super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2UserConsent.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2UserConsent.java
new file mode 100644
index 000000000..684ab2bb5
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OAuth2UserConsent.java
@@ -0,0 +1,47 @@
+
+package com.baeldung.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;
+
+    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;
+    }
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OidcAuthorizationCodeGrantAuthorization.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OidcAuthorizationCodeGrantAuthorization.java
new file mode 100644
index 000000000..a360a226b
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/entity/OidcAuthorizationCodeGrantAuthorization.java
@@ -0,0 +1,40 @@
+
+package com.baeldung.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;
+
+    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;
+        }
+
+    }
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java
new file mode 100644
index 000000000..0f2f926c0
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2AuthorizationGrantAuthorizationRepository.java
@@ -0,0 +1,37 @@
+package com.baeldung.redis.repository;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.redis.entity.OAuth2AuthorizationCodeGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2AuthorizationGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2DeviceCodeGrantAuthorization;
+import com.baeldung.redis.entity.OidcAuthorizationCodeGrantAuthorization;
+
+@Repository
+public interface OAuth2AuthorizationGrantAuthorizationRepository
+		extends CrudRepository<OAuth2AuthorizationGrantAuthorization, String> {
+
+	<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByState(String state);
+
+	<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByAuthorizationCode_TokenValue(String authorizationCode);
+
+	<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByStateOrAuthorizationCode_TokenValue(String state, String authorizationCode);
+
+	<T extends OAuth2AuthorizationGrantAuthorization> T findByAccessToken_TokenValue(String accessToken);
+
+	<T extends OAuth2AuthorizationGrantAuthorization> T findByRefreshToken_TokenValue(String refreshToken);
+
+	<T extends OAuth2AuthorizationGrantAuthorization> T findByAccessToken_TokenValueOrRefreshToken_TokenValue(String accessToken, String refreshToken);
+
+	<T extends OidcAuthorizationCodeGrantAuthorization> T findByIdToken_TokenValue(String idToken);
+
+	<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceState(String deviceState);
+
+	<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceCode_TokenValue(String deviceCode);
+
+	<T extends OAuth2DeviceCodeGrantAuthorization> T findByUserCode_TokenValue(String userCode);
+
+	<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue(String deviceState, String deviceCode, String userCode);
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2RegisteredClientRepository.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2RegisteredClientRepository.java
new file mode 100644
index 000000000..259abcd3d
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2RegisteredClientRepository.java
@@ -0,0 +1,13 @@
+package com.baeldung.redis.repository;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.redis.entity.OAuth2RegisteredClient;
+
+@Repository
+public interface OAuth2RegisteredClientRepository extends CrudRepository<OAuth2RegisteredClient, String> {
+
+    OAuth2RegisteredClient findByClientId(String clientId);
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2UserConsentRepository.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2UserConsentRepository.java
new file mode 100644
index 000000000..bab7a6f01
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/repository/OAuth2UserConsentRepository.java
@@ -0,0 +1,15 @@
+package com.baeldung.redis.repository;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+import com.baeldung.redis.entity.OAuth2UserConsent;
+
+@Repository
+public interface OAuth2UserConsentRepository extends CrudRepository<OAuth2UserConsent, String> {
+
+    OAuth2UserConsent findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
+
+    void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/ModelMapper.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/ModelMapper.java
new file mode 100644
index 000000000..3fcce02ed
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/ModelMapper.java
@@ -0,0 +1,483 @@
+package com.baeldung.redis.service;
+
+import java.security.Principal;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2AuthorizationCodeGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2AuthorizationGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2ClientCredentialsGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2DeviceCodeGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2RegisteredClient;
+import com.baeldung.redis.entity.OAuth2TokenExchangeGrantAuthorization;
+import com.baeldung.redis.entity.OAuth2UserConsent;
+import com.baeldung.redis.entity.OidcAuthorizationCodeGrantAuthorization;
+
+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()));
+	}
+
+}
diff --git a/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationConsentService.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationConsentService.java
new file mode 100644
index 000000000..b727dd1e6
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationConsentService.java
@@ -0,0 +1,43 @@
+package com.baeldung.redis.service;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2UserConsent;
+import com.baeldung.redis.repository.OAuth2UserConsentRepository;
+
+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/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationService.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationService.java
new file mode 100644
index 000000000..f1aa290a9
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisOAuth2AuthorizationService.java
@@ -0,0 +1,98 @@
+package com.baeldung.redis.service;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2AuthorizationGrantAuthorization;
+import com.baeldung.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
+
+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_TokenValue(token, token);
+            if (authorizationGrantAuthorization == null) {
+                authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository.findByAccessToken_TokenValueOrRefreshToken_TokenValue(token,
+                    token);
+            }
+            if (authorizationGrantAuthorization == null) {
+                authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository.findByIdToken_TokenValue(token);
+            }
+            if (authorizationGrantAuthorization == null) {
+                authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository.findByDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue(
+                    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/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisRegisteredClientRepository.java b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisRegisteredClientRepository.java
new file mode 100644
index 000000000..db41f89b9
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/java/com/baeldung/redis/service/RedisRegisteredClientRepository.java
@@ -0,0 +1,44 @@
+package com.baeldung.redis.service;
+
+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;
+
+import com.baeldung.redis.entity.OAuth2RegisteredClient;
+import com.baeldung.redis.repository.OAuth2RegisteredClientRepository;
+
+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/redis-authorization-server/redis-oauth-server/src/main/resources/application.yml b/redis-authorization-server/redis-oauth-server/src/main/resources/application.yml
new file mode 100644
index 000000000..ca8cbeef6
--- /dev/null
+++ b/redis-authorization-server/redis-oauth-server/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+server:
+  port: 9000
+
+logging:
+  level:
+    root: INFO
+    org.springframework.web: INFO
+    org.springframework.security: INFO
+    org.springframework.security.oauth2: INFO
+    org.springframework.boot: INFO
+
+spring:
+  security:
+    oauth2:
+      authorizationserver:
+        issuer: http://localhost:9000
\ No newline at end of file
diff --git a/redis-authorization-server/redis-resource-server/.gitignore b/redis-authorization-server/redis-resource-server/.gitignore
new file mode 100644
index 000000000..0449f1468
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/.gitignore
@@ -0,0 +1,4 @@
+/.classpath
+/.project
+/target/
+/.settings/
diff --git a/redis-authorization-server/redis-resource-server/pom.xml b/redis-authorization-server/redis-resource-server/pom.xml
new file mode 100644
index 000000000..b32c3b2c7
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/pom.xml
@@ -0,0 +1,62 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.baeldung</groupId>
+    <artifactId>resource-server</artifactId>
+    <name>resource-server</name>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.3</version>
+        <relativePath />
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*LiveTest.java</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <developers>
+        <developer>
+            <email>eugen@baeldung.com</email>
+            <name>Eugen Paraschiv</name>
+            <url>https://github.com/eugenp</url>
+            <id>eugenp</id>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java.version>17</java.version>
+    </properties>
+
+</project>
diff --git a/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/ResourceServerApplication.java b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/ResourceServerApplication.java
new file mode 100644
index 000000000..0004de6c7
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/ResourceServerApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.resource;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ResourceServerApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ResourceServerApplication.class, args);
+    }
+}
diff --git a/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/config/ResourceServerConfig.java b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/config/ResourceServerConfig.java
new file mode 100644
index 000000000..74923360f
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/config/ResourceServerConfig.java
@@ -0,0 +1,22 @@
+package com.baeldung.resource.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+public class ResourceServerConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.securityMatcher("/articles/**")
+            .authorizeHttpRequests(authorize -> authorize.anyRequest()
+                .hasAuthority("SCOPE_articles.read"))
+            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
+        return http.build();
+    }
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/web/ArticlesController.java b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/web/ArticlesController.java
new file mode 100644
index 000000000..10c58fe70
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/src/main/java/com/baeldung/resource/web/ArticlesController.java
@@ -0,0 +1,13 @@
+package com.baeldung.resource.web;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ArticlesController {
+
+    @GetMapping("/articles")
+    public String[] getArticles() {
+        return new String[]{"Article 1", "Article 2", "Article 3"};
+    }
+}
\ No newline at end of file
diff --git a/redis-authorization-server/redis-resource-server/src/main/resources/application.yml b/redis-authorization-server/redis-resource-server/src/main/resources/application.yml
new file mode 100644
index 000000000..1b76f2686
--- /dev/null
+++ b/redis-authorization-server/redis-resource-server/src/main/resources/application.yml
@@ -0,0 +1,18 @@
+
+server:
+  port: 8090
+
+logging:
+  level:
+    root: INFO
+    org.springframework.web: INFO
+    org.springframework.security: INFO
+    org.springframework.security.oauth2: INFO
+    org.springframework.boot: INFO
+
+spring:
+  security:
+    oauth2:
+      resourceserver:
+        jwt:
+          issuer-uri: http://localhost:9000
\ No newline at end of file

From 6d8ebe4970625c37fc1bc4b32d16621c27dfc77d Mon Sep 17 00:00:00 2001
From: Omkar A <omkara.adg@gmail.com>
Date: Fri, 21 Mar 2025 19:10:52 +0530
Subject: [PATCH 2/2] -- changed snapshot name to avoid conflict

---
 redis-authorization-server/redis-auth-client/pom.xml     | 4 ++--
 redis-authorization-server/redis-resource-server/pom.xml | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/redis-authorization-server/redis-auth-client/pom.xml b/redis-authorization-server/redis-auth-client/pom.xml
index c0fbbf6aa..19058ef32 100644
--- a/redis-authorization-server/redis-auth-client/pom.xml
+++ b/redis-authorization-server/redis-auth-client/pom.xml
@@ -4,8 +4,8 @@
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.baeldung</groupId>
-    <artifactId>auth-client</artifactId>
-    <name>auth-client</name>
+    <artifactId>redis-auth-client</artifactId>
+    <name>redis-auth-client</name>
     <version>0.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
diff --git a/redis-authorization-server/redis-resource-server/pom.xml b/redis-authorization-server/redis-resource-server/pom.xml
index b32c3b2c7..ae16d32c5 100644
--- a/redis-authorization-server/redis-resource-server/pom.xml
+++ b/redis-authorization-server/redis-resource-server/pom.xml
@@ -4,8 +4,8 @@
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.baeldung</groupId>
-    <artifactId>resource-server</artifactId>
-    <name>resource-server</name>
+    <artifactId>redis-resource-server</artifactId>
+    <name>redis-resource-server</name>
     <version>0.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>