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>