├── multiple-issuers ├── message │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── message │ │ │ │ ├── IssuerRepository.java │ │ │ │ ├── MessageApplication.java │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── CurrentUserId.java │ │ │ │ ├── MongoIssuerInitializer.java │ │ │ │ ├── Issuer.java │ │ │ │ ├── MongoMessageInitiailizer.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ └── SecurityConfig.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── issuers │ │ │ └── message │ │ │ └── MessageApplicationTests.java │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── pom.xml ├── user │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── user │ │ │ │ ├── UserRepository.java │ │ │ │ ├── UserApplication.java │ │ │ │ ├── MongoUserInitiailizer.java │ │ │ │ ├── UserController.java │ │ │ │ └── User.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── issuers │ │ │ └── user │ │ │ └── UserApplicationTests.java │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── pom.xml ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── inbox │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── src │ │ ├── main │ │ ├── java │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── inbox │ │ │ │ ├── user │ │ │ │ ├── UserService.java │ │ │ │ ├── UserController.java │ │ │ │ ├── WebClientUserService.java │ │ │ │ └── User.java │ │ │ │ ├── IndexController.java │ │ │ │ ├── security │ │ │ │ ├── LoginController.java │ │ │ │ ├── SecurityControllerAdvice.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ └── UserServiceOAuth2UserService.java │ │ │ │ ├── message │ │ │ │ ├── MessageService.java │ │ │ │ ├── MessageDto.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ └── WebClientMessageService.java │ │ │ │ └── InboxApplication.java │ │ └── resources │ │ │ ├── templates │ │ │ ├── messages │ │ │ │ ├── view.html │ │ │ │ └── inbox.html │ │ │ ├── login.html │ │ │ └── users │ │ │ │ └── form.html │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── sample │ │ └── issuers │ │ └── inbox │ │ ├── InboxApplicationTests.java │ │ └── user │ │ └── WebClientUserServiceTest.java ├── reset ├── etc │ └── Dockerfile ├── gateway │ └── src │ │ └── main │ │ ├── resources │ │ └── application.properties │ │ └── java │ │ └── sample │ │ └── issuers │ │ └── gateway │ │ ├── SecurityConfig.java │ │ ├── GatewayApplication.java │ │ └── IdFilter.java └── pom.xml ├── multiple-issuers-webflux ├── user │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── webflux │ │ │ │ └── user │ │ │ │ ├── UserApplication.java │ │ │ │ ├── UserRepository.java │ │ │ │ ├── MongoUserInitiailizer.java │ │ │ │ ├── UserController.java │ │ │ │ └── User.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── issuers │ │ │ └── webflux │ │ │ └── user │ │ │ └── UserApplicationTests.java │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── pom.xml ├── message │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── webflux │ │ │ │ └── message │ │ │ │ ├── MessageApplication.java │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── CurrentUserId.java │ │ │ │ ├── MongoMessageInitiailizer.java │ │ │ │ ├── Message.java │ │ │ │ └── MessageController.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── issuers │ │ │ └── webflux │ │ │ └── message │ │ │ └── MessageApplicationTests.java │ └── .mvn │ │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── inbox │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── src │ │ ├── main │ │ ├── java │ │ │ └── sample │ │ │ │ └── issuers │ │ │ │ └── webflux │ │ │ │ └── inbox │ │ │ │ ├── user │ │ │ │ ├── UserService.java │ │ │ │ ├── UserController.java │ │ │ │ ├── WebClientUserService.java │ │ │ │ └── User.java │ │ │ │ ├── message │ │ │ │ ├── MessageService.java │ │ │ │ ├── MessageDto.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ └── WebClientMessageService.java │ │ │ │ ├── IndexController.java │ │ │ │ ├── security │ │ │ │ ├── LoginController.java │ │ │ │ ├── SecurityControllerAdvice.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ └── ServiceReactiveOAuth2UserService.java │ │ │ │ └── InboxApplication.java │ │ └── resources │ │ │ ├── templates │ │ │ ├── messages │ │ │ │ ├── view.html │ │ │ │ └── inbox.html │ │ │ ├── login.html │ │ │ └── users │ │ │ │ └── form.html │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── sample │ │ └── issuers │ │ └── webflux │ │ └── inbox │ │ ├── InboxApplicationTests.java │ │ └── user │ │ └── WebClientUserServiceTest.java ├── reset ├── etc │ └── Dockerfile ├── gateway │ └── src │ │ └── main │ │ ├── resources │ │ └── application.properties │ │ └── java │ │ └── sample │ │ └── issuers │ │ └── webflux │ │ └── gateway │ │ ├── SecurityConfig.java │ │ ├── GatewayApplication.java │ │ └── IdFilter.java └── pom.xml ├── tenant-domain-object ├── tenant │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── tenants │ │ │ │ └── user │ │ │ │ ├── TenantRepository.java │ │ │ │ ├── TenantApplication.java │ │ │ │ ├── TenantInitiailizer.java │ │ │ │ ├── TenantController.java │ │ │ │ └── Tenant.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── tenants │ │ │ └── user │ │ │ └── TenantApplicationTests.java │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── pom.xml ├── user │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── application.yml │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── tenants │ │ │ │ └── user │ │ │ │ ├── UserRepository.java │ │ │ │ ├── UserApplication.java │ │ │ │ ├── User.java │ │ │ │ ├── UserInitializer.java │ │ │ │ ├── UserController.java │ │ │ │ └── UserSecurityConfig.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── tenants │ │ │ └── user │ │ │ └── UserApplicationTests.java │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── pom.xml ├── message │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.yml │ │ │ │ └── import.sql │ │ │ └── java │ │ │ │ └── sample │ │ │ │ └── tenants │ │ │ │ └── message │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── CurrentUserId.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ ├── MessageInitializer.java │ │ │ │ ├── MessageApplication.java │ │ │ │ └── MessageSecurityConfig.java │ │ └── test │ │ │ └── java │ │ │ └── sample │ │ │ └── tenants │ │ │ └── message │ │ │ └── MessageApplicationTests.java │ └── .mvn │ │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── inbox │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.properties │ │ │ └── maven-wrapper.jar │ └── src │ │ ├── main │ │ ├── java │ │ │ └── sample │ │ │ │ └── tenants │ │ │ │ └── inbox │ │ │ │ ├── user │ │ │ │ ├── UserService.java │ │ │ │ ├── UserController.java │ │ │ │ ├── WebClientUserService.java │ │ │ │ └── User.java │ │ │ │ ├── message │ │ │ │ ├── MessageService.java │ │ │ │ ├── MessageDto.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ └── WebClientMessageService.java │ │ │ │ ├── IndexController.java │ │ │ │ ├── security │ │ │ │ ├── LoginController.java │ │ │ │ ├── SecurityControllerAdvice.java │ │ │ │ ├── UserServiceOAuth2UserService.java │ │ │ │ └── SecurityConfig.java │ │ │ │ └── InboxApplication.java │ │ └── resources │ │ │ ├── templates │ │ │ ├── messages │ │ │ │ ├── view.html │ │ │ │ └── inbox.html │ │ │ ├── login.html │ │ │ └── users │ │ │ │ └── form.html │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── sample │ │ └── tenants │ │ └── inbox │ │ ├── InboxApplicationTests.java │ │ └── user │ │ └── WebClientUserServiceTest.java ├── reset ├── etc │ └── Dockerfile ├── gateway │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.properties │ │ │ └── java │ │ │ └── sample │ │ │ └── tenants │ │ │ └── gateway │ │ │ ├── SecurityConfig.java │ │ │ ├── GatewayApplication.java │ │ │ └── IdFilter.java │ └── pom.xml └── pom.xml ├── README.md ├── multi-tenancy ├── src │ └── main │ │ └── java │ │ └── sample │ │ └── multitenancy │ │ ├── TenantRepository.java │ │ ├── TenantHolder.java │ │ ├── web │ │ ├── SubdomainTenantIdentifierResolver.java │ │ ├── HeaderTenantIdentifierResolver.java │ │ ├── TenantHeaderExchangeFilterFunction.java │ │ ├── WebClientTenantRepository.java │ │ └── TenantResolverFilter.java │ │ ├── schema │ │ ├── TenantCurrentTenantIdentifierResolver.java │ │ ├── TenantHolderMultiTenantConnectionProvider.java │ │ └── MultiTenantSchemaManagementTool.java │ │ ├── discriminator │ │ └── TenantAspect.java │ │ ├── oauth2 │ │ └── TenantClientRegistrationRepository.java │ │ └── Tenant.java └── pom.xml ├── .gitignore └── pom.xml /multiple-issuers/message/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8083 -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multi-tenant-security-samples 2 | Sample Applications Demonstrating Multi-tenancy in Spring Security 3 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | tenants-url: http://localhost:8083/tenants -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | 4 | tenants-url: http://localhost:8083/tenants -------------------------------------------------------------------------------- /multiple-issuers/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers/user/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /tenant-domain-object/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers/message/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers/user/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers/user/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/tenant-domain-object/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/inbox/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /tenant-domain-object/user/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers-webflux/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers/inbox/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers/message/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers/message/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/message/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/tenant-domain-object/inbox/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/user/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/tenant-domain-object/user/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/TenantRepository.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy; 2 | 3 | public interface TenantRepository { 4 | Tenant findByAlias(String alias); 5 | } 6 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers-webflux/user/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/message/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/tenant-domain-object/message/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tenant-domain-object/tenant/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/tenant-domain-object/tenant/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers-webflux/inbox/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jzheaux/multi-tenant-security-samples/HEAD/multiple-issuers-webflux/message/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /multiple-issuers/reset: -------------------------------------------------------------------------------- 1 | git stash && git stash drop 2 | git checkout 6b4944841967d371ec0574418d65a0340a48bc76 3 | gateway/mvnw clean 4 | inbox/mvnw clean 5 | message/mvnw clean 6 | user/mvnw clean 7 | -------------------------------------------------------------------------------- /tenant-domain-object/reset: -------------------------------------------------------------------------------- 1 | git stash && git stash drop 2 | git checkout 6b4944841967d371ec0574418d65a0340a48bc76 3 | gateway/mvnw clean 4 | inbox/mvnw clean 5 | message/mvnw clean 6 | user/mvnw clean 7 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/reset: -------------------------------------------------------------------------------- 1 | git stash && git stash drop 2 | git checkout 6b4944841967d371ec0574418d65a0340a48bc76 3 | gateway/mvnw clean 4 | inbox/mvnw clean 5 | message/mvnw clean 6 | user/mvnw clean 7 | -------------------------------------------------------------------------------- /multiple-issuers/etc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/keycloak:4.1.0.Final 2 | 3 | ADD keycloak-realm.json /tmp/demo-realm.json 4 | 5 | ENV DB_VENDOR=H2 KEYCLOAK_USER=admin KEYCLOAK_PASSWORD=password 6 | 7 | CMD ["-b", "0.0.0.0", "-Dkeycloak.import=/tmp/demo-realm.json"] 8 | -------------------------------------------------------------------------------- /tenant-domain-object/etc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/keycloak:4.1.0.Final 2 | 3 | ADD keycloak-realm.json /tmp/demo-realm.json 4 | 5 | ENV DB_VENDOR=H2 KEYCLOAK_USER=admin KEYCLOAK_PASSWORD=password 6 | 7 | CMD ["-b", "0.0.0.0", "-Dkeycloak.import=/tmp/demo-realm.json"] 8 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/etc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/keycloak:4.1.0.Final 2 | 3 | ADD keycloak-realm.json /tmp/demo-realm.json 4 | 5 | ENV DB_VENDOR=H2 KEYCLOAK_USER=admin KEYCLOAK_PASSWORD=password 6 | 7 | CMD ["-b", "0.0.0.0", "-Dkeycloak.import=/tmp/demo-realm.json"] 8 | -------------------------------------------------------------------------------- /multiple-issuers/gateway/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | logging.level.org.springframework.cloud.gateway=TRACE 3 | management.endpoints.web.exposure.include=* 4 | 5 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://idp:9999/auth/realms/demo -------------------------------------------------------------------------------- /tenant-domain-object/gateway/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | logging.level.org.springframework.cloud.gateway=TRACE 3 | management.endpoints.web.exposure.include=* 4 | 5 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://idp:9999/auth/realms/demo -------------------------------------------------------------------------------- /multiple-issuers-webflux/gateway/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | logging.level.org.springframework.cloud.gateway=TRACE 3 | management.endpoints.web.exposure.include=* 4 | 5 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://idp:9999/auth/realms/demo -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/java/sample/tenants/user/TenantRepository.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | /** 6 | * @author Josh Cummings 7 | */ 8 | public interface TenantRepository extends CrudRepository { 9 | Tenant findByAlias(String alias); 10 | } 11 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/user/UserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.user; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public interface UserService { 9 | Mono save(User user); 10 | Mono findByEmail(String email); 11 | Mono findById(String id); 12 | } 13 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/user/UserService.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.user; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public interface UserService { 9 | Mono save(User user); 10 | Mono findByEmail(String email); 11 | Mono findById(String id); 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/java/sample/issuers/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public interface UserRepository extends CrudRepository { 9 | User findByEmail(String email); 10 | 11 | User findByAlias(String alias); 12 | } 13 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public interface UserRepository extends CrudRepository { 9 | User findByEmail(String email); 10 | 11 | User findByAlias(String alias); 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/user/UserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.user; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public interface UserService { 9 | Mono save(User user); 10 | Mono findByEmail(String email); 11 | Mono findById(String id); 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/IssuerRepository.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | /** 8 | * @author Josh Cummings 9 | */ 10 | public interface IssuerRepository extends CrudRepository { 11 | Optional findByUri(String uri); 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | classes/ 2 | target/ 3 | */src/*/java/META-INF 4 | */src/META-INF/ 5 | */src/*/java/META-INF/ 6 | .classpath 7 | .springBeans 8 | .project 9 | .DS_Store 10 | .settings/ 11 | .idea/ 12 | out/ 13 | bin/ 14 | intellij/ 15 | build/ 16 | *.log 17 | *.log.* 18 | *.iml 19 | *.ipr 20 | *.iws 21 | .gradle/ 22 | atlassian-ide-plugin.xml 23 | !etc/eclipse/.checkstyle 24 | .checkstyle 25 | s101plugin.state 26 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/message/MessageService.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.message; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | public interface MessageService { 10 | Flux inbox(); 11 | Mono findById(String id); 12 | Mono deleteById(String id); 13 | } 14 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/message/MessageService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.message; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | public interface MessageService { 10 | Flux inbox(); 11 | Mono findById(String id); 12 | Mono deleteById(String id); 13 | } 14 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/IndexController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class IndexController { 11 | @GetMapping("/") 12 | String index() { 13 | return "redirect:/messages/inbox"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/java/sample/issuers/user/UserApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/IndexController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class IndexController { 11 | @GetMapping("/") 12 | String index() { 13 | return "redirect:/messages/inbox"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/security/LoginController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.security; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class LoginController { 11 | @GetMapping("/login") 12 | public String login() { 13 | return "login"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/java/sample/tenants/user/TenantApplication.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TenantApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TenantApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/message/MessageService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.message; 2 | 3 | import java.util.Optional; 4 | 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | public interface MessageService { 12 | Flux inbox(); 13 | Mono findById(String id); 14 | Mono deleteById(String id); 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/MessageApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MessageApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MessageApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | public interface MessageRepository extends CrudRepository { 11 | Iterable findByTo(String id); 12 | 13 | Optional findById(Long id); 14 | } 15 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/security/LoginController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.security; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class LoginController { 11 | @GetMapping("/login") 12 | public String login() { 13 | return "login"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/java/sample/issuers/webflux/user/UserApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | public interface MessageRepository extends CrudRepository { 11 | Iterable findByTo(String id); 12 | 13 | Optional findById(Long id); 14 | } 15 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/IndexController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class IndexController { 11 | @GetMapping("/") 12 | String index() { 13 | return "redirect:/messages/inbox"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/TenantHolder.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy; 2 | 3 | public class TenantHolder { 4 | private static final ThreadLocal context = new ThreadLocal<>(); 5 | 6 | public static Tenant getTenant() { 7 | return context.get(); 8 | } 9 | 10 | public static void setTenant(Tenant tenant) { 11 | context.set(tenant); 12 | } 13 | 14 | public static void clearTenant() { 15 | context.remove(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/security/LoginController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.security; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Controller 10 | public class LoginController { 11 | @GetMapping("/login") 12 | public String login() { 13 | return "login"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/MessageApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MessageApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MessageApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/java/sample/issuers/webflux/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | public interface UserRepository extends ReactiveCrudRepository { 10 | Mono findByEmail(String email); 11 | 12 | Mono findByAlias(String alias); 13 | } 14 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/test/java/sample/issuers/inbox/InboxApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class InboxApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/test/java/sample/issuers/user/UserApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @SpringBootTest 10 | @RunWith(SpringRunner.class) 11 | public class UserApplicationTests { 12 | 13 | @Test 14 | public void contextLoads() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/test/java/sample/tenants/user/UserApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @SpringBootTest 9 | @RunWith(SpringRunner.class) 10 | public class UserApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/test/java/sample/tenants/inbox/InboxApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class InboxApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/test/java/sample/tenants/user/TenantApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @SpringBootTest 9 | @RunWith(SpringRunner.class) 10 | public class TenantApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/test/java/sample/issuers/webflux/user/UserApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @SpringBootTest 9 | @RunWith(SpringRunner.class) 10 | public class UserApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/test/java/sample/issuers/webflux/inbox/InboxApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class InboxApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/CurrentUserId.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import org.springframework.security.core.annotation.CurrentSecurityContext; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @CurrentSecurityContext(expression="authentication.tokenAttributes['user_id']") 14 | public @interface CurrentUserId { 15 | } 16 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/CurrentUserId.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import org.springframework.security.core.annotation.CurrentSecurityContext; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @CurrentSecurityContext(expression="authentication.tokenAttributes['user_id']") 14 | public @interface CurrentUserId { 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | public interface MessageRepository extends ReactiveCrudRepository { 12 | Flux findByTo(String id); 13 | 14 | Mono findById(Long id); 15 | } 16 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/CurrentUserId.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import org.springframework.security.core.annotation.CurrentSecurityContext; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @CurrentSecurityContext(expression="authentication.tokenAttributes['user_id']") 14 | public @interface CurrentUserId { 15 | } 16 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS widgets; 2 | CREATE SCHEMA IF NOT EXISTS toasters; 3 | CREATE SCHEMA IF NOT EXISTS socks; 4 | 5 | CREATE TABLE widgets.message (id BIGINT PRIMARY KEY, to VARCHAR2(256) NOT NULL, from_address VARCHAR2(256) NOT NULL, text TEXT NOT NULL); 6 | CREATE TABLE toasters.message (id BIGINT PRIMARY KEY, to VARCHAR2(256) NOT NULL, from_address VARCHAR2(256) NOT NULL, text TEXT NOT NULL); 7 | CREATE TABLE socks.message (id BIGINT PRIMARY KEY, to VARCHAR2(256) NOT NULL, from_address VARCHAR2(256) NOT NULL, text TEXT NOT NULL); -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/security/SecurityControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.security; 2 | 3 | import sample.issuers.inbox.user.User; 4 | 5 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ModelAttribute; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @ControllerAdvice 13 | public class SecurityControllerAdvice { 14 | 15 | @ModelAttribute("currentUser") 16 | User currentUser(@AuthenticationPrincipal User currentUser) { 17 | return currentUser; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/security/SecurityControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.security; 2 | 3 | import sample.tenants.inbox.user.User; 4 | 5 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ModelAttribute; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @ControllerAdvice 13 | public class SecurityControllerAdvice { 14 | 15 | @ModelAttribute("currentUser") 16 | User currentUser(@AuthenticationPrincipal User currentUser) { 17 | return currentUser; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/web/SubdomainTenantIdentifierResolver.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.web; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import org.springframework.core.convert.converter.Converter; 6 | 7 | public class SubdomainTenantIdentifierResolver implements Converter { 8 | @Override 9 | public String convert(HttpServletRequest request) { 10 | String serverName = request.getServerName(); 11 | if (serverName == null) { 12 | return null; 13 | } 14 | String[] segments = serverName.split("\\."); 15 | if (segments.length == 0) { 16 | return null; 17 | } 18 | return segments[0]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/web/HeaderTenantIdentifierResolver.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.web; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import org.springframework.core.convert.converter.Converter; 6 | 7 | public class HeaderTenantIdentifierResolver implements Converter { 8 | private final String headerName; 9 | 10 | public HeaderTenantIdentifierResolver() { 11 | this("X-Tenant-Alias"); 12 | } 13 | 14 | public HeaderTenantIdentifierResolver(String headerName) { 15 | this.headerName = headerName; 16 | } 17 | 18 | @Override 19 | public String convert(HttpServletRequest request) { 20 | return request.getHeader(this.headerName); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/schema/TenantCurrentTenantIdentifierResolver.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.schema; 2 | 3 | import java.util.Optional; 4 | 5 | import org.hibernate.context.spi.CurrentTenantIdentifierResolver; 6 | import sample.multitenancy.Tenant; 7 | import sample.multitenancy.TenantHolder; 8 | 9 | public class TenantCurrentTenantIdentifierResolver 10 | implements CurrentTenantIdentifierResolver { 11 | 12 | @Override 13 | public String resolveCurrentTenantIdentifier() { 14 | return Optional.ofNullable(TenantHolder.getTenant()) 15 | .orElse(Tenant.DEFAULT_TENANT).getName(); 16 | } 17 | 18 | @Override 19 | public boolean validateExistingCurrentSessions() { 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/security/SecurityControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.security; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | import org.springframework.web.bind.annotation.ControllerAdvice; 5 | import org.springframework.web.bind.annotation.ModelAttribute; 6 | import reactor.core.publisher.Mono; 7 | 8 | import sample.issuers.webflux.inbox.user.User; 9 | 10 | /** 11 | * @author Rob Winch 12 | */ 13 | @ControllerAdvice 14 | public class SecurityControllerAdvice { 15 | 16 | @ModelAttribute("currentUser") 17 | Mono currentUser(@AuthenticationPrincipal Mono currentUser) { 18 | return currentUser; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/test/java/sample/tenants/message/MessageApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | public class MessageApplicationTests { 14 | @Autowired 15 | private MessageRepository messages; 16 | 17 | @Test 18 | public void inbox() { 19 | Iterable inbox = this.messages.findByTo("1"); 20 | Assertions.assertThat(inbox).hasSize(0); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/test/java/sample/issuers/message/MessageApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(SpringRunner.class) 13 | @SpringBootTest 14 | public class MessageApplicationTests { 15 | @Autowired 16 | private MessageRepository messages; 17 | 18 | @Test 19 | public void inbox() { 20 | Iterable inbox = this.messages.findByTo("1"); 21 | assertThat(inbox).hasSize(5); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/test/java/sample/tenants/inbox/user/WebClientUserServiceTest.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.user; 2 | 3 | import org.junit.Test; 4 | 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | public class WebClientUserServiceTest { 11 | WebClientUserService users = new WebClientUserService(WebClient.create(), "http://localhost:8081/users"); 12 | 13 | @Test 14 | public void save() { 15 | /*User user = new User(null, "new@example.com", "password", "New", "User"); 16 | this.users.save(user).block();*/ 17 | } 18 | 19 | @Test 20 | public void findByEmail() { 21 | /*User rob = this.users.findByEmail("rob@example.com").block(); 22 | 23 | assertThat(rob).isNotNull();*/ 24 | } 25 | } -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/test/java/sample/issuers/webflux/inbox/user/WebClientUserServiceTest.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.user; 2 | 3 | import org.junit.Test; 4 | 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | public class WebClientUserServiceTest { 11 | WebClientUserService users = new WebClientUserService(WebClient.create(), "http://localhost:8081/users"); 12 | 13 | @Test 14 | public void save() { 15 | /*User user = new User(null, "new@example.com", "password", "New", "User"); 16 | this.users.save(user).block();*/ 17 | } 18 | 19 | @Test 20 | public void findByEmail() { 21 | /*User rob = this.users.findByEmail("rob@example.com").block(); 22 | 23 | assertThat(rob).isNotNull();*/ 24 | } 25 | } -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/test/java/sample/issuers/inbox/user/WebClientUserServiceTest.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.user; 2 | 3 | import org.junit.Test; 4 | import org.springframework.web.reactive.function.client.WebClient; 5 | 6 | import static org.assertj.core.api.Assertions.*; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | public class WebClientUserServiceTest { 12 | WebClientUserService users = new WebClientUserService(WebClient.create(), "http://localhost:8081/users"); 13 | 14 | @Test 15 | public void save() { 16 | /*User user = new User(null, "new@example.com", "password", "New", "User"); 17 | this.users.save(user).block();*/ 18 | } 19 | 20 | @Test 21 | public void findByEmail() { 22 | /*User rob = this.users.findByEmail("rob@example.com").block(); 23 | 24 | assertThat(rob).isNotNull();*/ 25 | } 26 | } -------------------------------------------------------------------------------- /multiple-issuers/gateway/src/main/java/sample/issuers/gateway/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.gateway; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.web.server.ServerHttpSecurity; 6 | import org.springframework.security.web.server.SecurityWebFilterChain; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | @Configuration 12 | public class SecurityConfig { 13 | @Bean 14 | SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) 15 | throws Exception { 16 | // @formatter:off 17 | http 18 | .authorizeExchange() 19 | .anyExchange().authenticated() 20 | .and() 21 | .oauth2ResourceServer() 22 | .jwt(); 23 | // @formatter:on 24 | return http.build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tenant-domain-object/gateway/src/main/java/sample/tenants/gateway/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.gateway; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.web.server.ServerHttpSecurity; 6 | import org.springframework.security.web.server.SecurityWebFilterChain; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | @Configuration 12 | public class SecurityConfig { 13 | @Bean 14 | SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) 15 | throws Exception { 16 | // @formatter:off 17 | http 18 | .authorizeExchange() 19 | .anyExchange().authenticated() 20 | .and() 21 | .oauth2ResourceServer() 22 | .jwt(); 23 | // @formatter:on 24 | return http.build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/test/java/sample/issuers/webflux/message/MessageApplicationTests.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class MessageApplicationTests { 16 | @Autowired 17 | private MessageRepository messages; 18 | 19 | @Test 20 | public void inbox() { 21 | List inbox = this.messages.findByTo("1").collectList().block(); 22 | assertThat(inbox).hasSize(5); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/gateway/src/main/java/sample/issuers/webflux/gateway/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.gateway; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.web.server.ServerHttpSecurity; 6 | import org.springframework.security.web.server.SecurityWebFilterChain; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | @Configuration 12 | public class SecurityConfig { 13 | @Bean 14 | SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) 15 | throws Exception { 16 | // @formatter:off 17 | http 18 | .authorizeExchange() 19 | .anyExchange().authenticated() 20 | .and() 21 | .oauth2ResourceServer() 22 | .jwt(); 23 | // @formatter:on 24 | return http.build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/resources/templates/messages/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Messages 6 | 7 | 8 | 9 |
10 |

From:

11 |
12 |

To:

13 |
14 |

Text:

15 |
16 |
17 | 18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/resources/templates/messages/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Messages 6 | 7 | 8 | 9 |
10 |

From:

11 |
12 |

To:

13 |
14 |

Text:

15 |
16 |
17 | 18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/resources/templates/messages/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Messages 6 | 7 | 8 | 9 |
10 |

From:

11 |
12 |

To:

13 |
14 |

Text:

15 |
16 |
17 | 18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | reator: 4 | netty: 5 | channel: DEBUG 6 | users-url: http://localhost:8081/users 7 | messages-url: http://localhost:8082/messages 8 | 9 | spring: 10 | security: 11 | oauth2: 12 | client: 13 | provider: 14 | tenantone: 15 | issuer-uri: http://idp:9999/auth/realms/tenantone 16 | user-name-attribute: email 17 | tenanttwo: 18 | issuer-uri: http://idp:9999/auth/realms/tenanttwo 19 | registration: 20 | tenantone: 21 | client-id: spring-security 22 | client-secret: d1a8feec-9505-4241-a2ef-32bbabdd8f98 23 | scope: openid,message:read 24 | tenanttwo: 25 | client-id: spring-security 26 | client-secret: bfbd9f62-02ce-4638-a370-80d45514bd0a -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | reator: 4 | netty: 5 | channel: DEBUG 6 | users-url: http://localhost:8081/users 7 | messages-url: http://localhost:8082/messages 8 | 9 | spring: 10 | security: 11 | oauth2: 12 | client: 13 | provider: 14 | tenantone: 15 | issuer-uri: http://idp:9999/auth/realms/tenantone 16 | user-name-attribute: email 17 | tenanttwo: 18 | issuer-uri: http://idp:9999/auth/realms/tenanttwo 19 | registration: 20 | tenantone: 21 | client-id: spring-security 22 | client-secret: d1a8feec-9505-4241-a2ef-32bbabdd8f98 23 | scope: openid,message:read 24 | tenanttwo: 25 | client-id: spring-security 26 | client-secret: bfbd9f62-02ce-4638-a370-80d45514bd0a -------------------------------------------------------------------------------- /multiple-issuers/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.multiple-issuers 8 | multiple-issuers 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | sample 14 | multi-tenancy-demos 15 | 0.0.1-SNAPSHOT 16 | 17 | 18 | 19 | user 20 | message 21 | inbox 22 | gateway 23 | 24 | 25 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/message/MessageDto.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.message; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | class MessageDto { 7 | private Long id; 8 | 9 | private String to; 10 | 11 | private String from; 12 | 13 | private String text; 14 | 15 | public MessageDto() {} 16 | 17 | public Long getId() { 18 | return this.id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getTo() { 26 | return this.to; 27 | } 28 | 29 | public void setTo(String to) { 30 | this.to = to; 31 | } 32 | 33 | public String getFrom() { 34 | return this.from; 35 | } 36 | 37 | public void setFrom(String from) { 38 | this.from = from; 39 | } 40 | 41 | public String getText() { 42 | return this.text; 43 | } 44 | 45 | public void setText(String text) { 46 | this.text = text; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/message/MessageDto.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.message; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | class MessageDto { 7 | private Long id; 8 | 9 | private String to; 10 | 11 | private String from; 12 | 13 | private String text; 14 | 15 | public MessageDto() {} 16 | 17 | public Long getId() { 18 | return this.id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getTo() { 26 | return this.to; 27 | } 28 | 29 | public void setTo(String to) { 30 | this.to = to; 31 | } 32 | 33 | public String getFrom() { 34 | return this.from; 35 | } 36 | 37 | public void setFrom(String from) { 38 | this.from = from; 39 | } 40 | 41 | public String getText() { 42 | return this.text; 43 | } 44 | 45 | public void setText(String text) { 46 | this.text = text; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.multiple-issuers-webflux 8 | multiple-issuers-webflux 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | sample 14 | multi-tenancy-demos 15 | 0.0.1-SNAPSHOT 16 | 17 | 18 | 19 | user 20 | message 21 | inbox 22 | gateway 23 | 24 | 25 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/message/MessageDto.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.message; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | class MessageDto { 7 | private Long id; 8 | 9 | private String to; 10 | 11 | private String from; 12 | 13 | private String text; 14 | 15 | public MessageDto() {} 16 | 17 | public Long getId() { 18 | return this.id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getTo() { 26 | return this.to; 27 | } 28 | 29 | public void setTo(String to) { 30 | this.to = to; 31 | } 32 | 33 | public String getFrom() { 34 | return this.from; 35 | } 36 | 37 | public void setFrom(String from) { 38 | this.from = from; 39 | } 40 | 41 | public String getText() { 42 | return this.text; 43 | } 44 | 45 | public void setText(String text) { 46 | this.text = text; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tenant-domain-object/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.tenants 8 | tenant-domain-object 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | sample 14 | multi-tenancy-demos 15 | 0.0.1-SNAPSHOT 16 | 17 | 18 | 19 | tenant 20 | user 21 | message 22 | inbox 23 | gateway 24 | 25 | 26 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/MongoIssuerInitializer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Josh Cummings 8 | */ 9 | @Component 10 | class MongoIssuerInitializer implements SmartInitializingSingleton { 11 | private final IssuerRepository issuers; 12 | 13 | MongoIssuerInitializer(IssuerRepository issuers) { 14 | this.issuers = issuers; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | this.issuers.save(new Issuer(1L, 20 | "http://idp:9999/auth/realms/tenantone", 21 | "http://idp:9999/auth/realms/tenantone/protocol/openid-connect/certs")); 22 | this.issuers.save(new Issuer(2L, 23 | "http://idp:9999/auth/realms/tenanttwo", 24 | "http://idp:9999/auth/realms/tenanttwo/protocol/openid-connect/certs")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Please Log In 9 | 10 | 11 |
13 |

Please Specify Your Tenant

14 | 16 | 22 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/java/sample/tenants/user/TenantInitiailizer.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Josh Cummings 8 | */ 9 | @Component 10 | class TenantInitiailizer implements SmartInitializingSingleton { 11 | private final TenantRepository tenants; 12 | 13 | TenantInitiailizer(TenantRepository users) { 14 | this.tenants = users; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | this.tenants.save(new Tenant(1L, "Wild Widgets", "widgets", "widgets", "http://idp:9999/auth/realms/widgets", "jwt")); 20 | this.tenants.save(new Tenant(2L, "Tasty Toasters", "toasters", "toasters", "http://idp:9999/auth/realms/toasters", "jwt")); 21 | this.tenants.save(new Tenant(3L, "Socks A Sizzlin'", "socks", "socks", "http://idp:9999/auth/realms/socks", "opaqueToken")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Please Log In 9 | 10 | 11 |
13 |

Please Specify Your Tenant

14 | 16 | 22 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Please Log In 9 | 10 | 11 |
13 |

Please Specify Your Tenant

14 | 16 | 22 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/discriminator/TenantAspect.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.discriminator; 2 | 3 | import java.util.Optional; 4 | import javax.persistence.EntityManager; 5 | import javax.persistence.PersistenceContext; 6 | 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Before; 9 | import org.hibernate.Filter; 10 | import org.hibernate.Session; 11 | import sample.multitenancy.TenantHolder; 12 | 13 | @Aspect 14 | public class TenantAspect { 15 | @PersistenceContext 16 | EntityManager entityManager; 17 | 18 | @Before("execution(* org.springframework.data.repository.Repository+.*(..))") 19 | public void addTenantFilter() { 20 | Optional.ofNullable(TenantHolder.getTenant()).ifPresent( 21 | tenant -> { 22 | Session session = this.entityManager.unwrap(Session.class); 23 | Filter filter = session.enableFilter("tenantFilter"); 24 | filter.setParameter("tenantId", tenant.getName()); 25 | } 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/UserApplication.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import sample.multitenancy.TenantRepository; 4 | import sample.multitenancy.discriminator.TenantAspect; 5 | import sample.multitenancy.web.WebClientTenantRepository; 6 | 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | 12 | @SpringBootApplication 13 | public class UserApplication { 14 | 15 | @Value("${tenants-url}") String tenantsUrl; 16 | 17 | @Bean 18 | TenantRepository tenantRepository() { 19 | return new WebClientTenantRepository(this.tenantsUrl); 20 | } 21 | 22 | @Bean 23 | TenantAspect tenantAspect() { 24 | return new TenantAspect(); 25 | } 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(UserApplication.class, args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/Issuer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | /** 7 | * @author Josh Cummings 8 | */ 9 | @Document 10 | public class Issuer { 11 | @Id 12 | private Long id; 13 | 14 | private String uri; 15 | 16 | private String jwkSetUri; 17 | 18 | public Issuer() {} 19 | 20 | public Issuer(Long id, String uri, String jwkSetUri) { 21 | this.id = id; 22 | this.uri = uri; 23 | this.jwkSetUri = jwkSetUri; 24 | } 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getUri() { 35 | return uri; 36 | } 37 | 38 | public void setUri(String uri) { 39 | this.uri = uri; 40 | } 41 | 42 | public String getJwkSetUri() { 43 | return jwkSetUri; 44 | } 45 | 46 | public void setJwkSetUri(String jwkSetUri) { 47 | this.jwkSetUri = jwkSetUri; 48 | } 49 | } -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import java.util.UUID; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import org.hibernate.annotations.Filter; 12 | import org.hibernate.annotations.FilterDef; 13 | import org.hibernate.annotations.ParamDef; 14 | 15 | 16 | /** 17 | * @author Josh Cummings 18 | */ 19 | @Data 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Entity 23 | @FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")}) 24 | @Filter(name = "tenantFilter", condition = "tenant_id = :tenantId") 25 | public class User { 26 | @Id 27 | @GeneratedValue 28 | private Long id; 29 | 30 | private String email; 31 | 32 | private String password; 33 | 34 | private String firstName; 35 | 36 | private String lastName; 37 | 38 | private String alias; 39 | 40 | private String tenantId; 41 | } 42 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.message; 2 | 3 | import sample.issuers.inbox.user.User; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public class Message { 9 | private Long id; 10 | 11 | private User to; 12 | 13 | private User from; 14 | 15 | private String text; 16 | 17 | public Message() {} 18 | 19 | public Message(Long id, User to, User from, String text) { 20 | this.id = id; 21 | this.to = to; 22 | this.from = from; 23 | this.text = text; 24 | } 25 | 26 | public Long getId() { 27 | return this.id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public User getTo() { 35 | return this.to; 36 | } 37 | 38 | public void setTo(User to) { 39 | this.to = to; 40 | } 41 | 42 | public User getFrom() { 43 | return this.from; 44 | } 45 | 46 | public void setFrom(User from) { 47 | this.from = from; 48 | } 49 | 50 | public String getText() { 51 | return this.text; 52 | } 53 | 54 | public void setText(String text) { 55 | this.text = text; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tenant-domain-object/gateway/src/main/java/sample/tenants/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @SpringBootApplication 12 | public class GatewayApplication { 13 | 14 | @Bean 15 | public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 16 | //@formatter:off 17 | return builder.routes() 18 | .route("users", r -> r.path("/users/**") 19 | .uri("http://localhost:8081")) 20 | .route("messages", r -> r.path("/messages/**") 21 | .uri("http://localhost:8082")) 22 | .build(); 23 | //@formatter:on 24 | } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(GatewayApplication.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.message; 2 | 3 | import sample.tenants.inbox.user.User; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public class Message { 9 | private Long id; 10 | 11 | private User to; 12 | 13 | private User from; 14 | 15 | private String text; 16 | 17 | public Message() {} 18 | 19 | public Message(Long id, User to, User from, String text) { 20 | this.id = id; 21 | this.to = to; 22 | this.from = from; 23 | this.text = text; 24 | } 25 | 26 | public Long getId() { 27 | return this.id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public User getTo() { 35 | return this.to; 36 | } 37 | 38 | public void setTo(User to) { 39 | this.to = to; 40 | } 41 | 42 | public User getFrom() { 43 | return this.from; 44 | } 45 | 46 | public void setFrom(User from) { 47 | this.from = from; 48 | } 49 | 50 | public String getText() { 51 | return this.text; 52 | } 53 | 54 | public void setText(String text) { 55 | this.text = text; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/MongoMessageInitiailizer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Component 10 | class MongoMessageInitiailizer implements SmartInitializingSingleton { 11 | private final MessageRepository messages; 12 | 13 | MongoMessageInitiailizer(MessageRepository messages) { 14 | this.messages = messages; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | String robId = "1"; 20 | String joeId = "2"; 21 | 22 | this.messages.save(new Message(1L, robId, joeId, "Hello World")); 23 | this.messages.save(new Message(2L, robId, joeId,"Greetings Spring Enthusiasts")); 24 | this.messages.save(new Message(3L, robId, joeId,"Hola")); 25 | this.messages.save(new Message(4L, robId, joeId,"Hey Java Devs")); 26 | this.messages.save(new Message(5L, robId, joeId,"Aloha")); 27 | 28 | this.messages.save(new Message(100L, joeId, robId,"Hey Joe")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/gateway/src/main/java/sample/issuers/webflux/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @SpringBootApplication 12 | public class GatewayApplication { 13 | 14 | @Bean 15 | public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 16 | //@formatter:off 17 | return builder.routes() 18 | .route("users", r -> r.path("/users/**") 19 | .uri("http://localhost:8081")) 20 | .route("messages", r -> r.path("/messages/**") 21 | .uri("http://localhost:8082")) 22 | .build(); 23 | //@formatter:on 24 | } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(GatewayApplication.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /multiple-issuers/gateway/src/main/java/sample/issuers/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @SpringBootApplication 12 | public class GatewayApplication { 13 | 14 | @Bean 15 | public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 16 | //@formatter:off 17 | return builder.routes() 18 | .route("users", r -> r.path("/users/**") 19 | .uri("http://localhost:8081")) 20 | .route("messages", r -> r.path("/messages/**") 21 | .uri("http://localhost:8082")) 22 | .build(); 23 | //@formatter:on 24 | } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(sample.issuers.gateway.GatewayApplication.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.message; 2 | 3 | import sample.issuers.webflux.inbox.user.User; 4 | 5 | /** 6 | * @author Rob Winch 7 | */ 8 | public class Message { 9 | private Long id; 10 | 11 | private User to; 12 | 13 | private User from; 14 | 15 | private String text; 16 | 17 | public Message() {} 18 | 19 | public Message(Long id, User to, User from, String text) { 20 | this.id = id; 21 | this.to = to; 22 | this.from = from; 23 | this.text = text; 24 | } 25 | 26 | public Long getId() { 27 | return this.id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public User getTo() { 35 | return this.to; 36 | } 37 | 38 | public void setTo(User to) { 39 | this.to = to; 40 | } 41 | 42 | public User getFrom() { 43 | return this.from; 44 | } 45 | 46 | public void setFrom(User from) { 47 | this.from = from; 48 | } 49 | 50 | public String getText() { 51 | return this.text; 52 | } 53 | 54 | public void setText(String text) { 55 | this.text = text; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | reator: 4 | netty: 5 | channel: DEBUG 6 | users-url: http://localhost:8081/users 7 | messages-url: http://localhost:8082/messages 8 | tenants-url: http://localhost:8083/tenants 9 | 10 | spring: 11 | security: 12 | oauth2: 13 | client: 14 | provider: 15 | widgets: 16 | issuer-uri: http://idp:9999/auth/realms/widgets 17 | user-name-attribute: email 18 | toasters: 19 | issuer-uri: http://idp:9999/auth/realms/toasters 20 | socks: 21 | issuer-uri: http://idp:9999/auth/realms/socks 22 | registration: 23 | widgets: 24 | client-id: spring-security 25 | client-secret: d1a8feec-9505-4241-a2ef-32bbabdd8f98 26 | scope: openid,message:read 27 | toasters: 28 | client-id: spring-security 29 | client-secret: bfbd9f62-02ce-4638-a370-80d45514bd0a 30 | socks: 31 | client-id: spring-security 32 | client-secret: bfbd9f62-02ce-4638-a370-80d45514bd0a -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/web/TenantHeaderExchangeFilterFunction.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.web; 2 | 3 | import reactor.core.publisher.Mono; 4 | import sample.multitenancy.TenantHolder; 5 | 6 | import org.springframework.web.reactive.function.client.ClientRequest; 7 | import org.springframework.web.reactive.function.client.ClientResponse; 8 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 9 | import org.springframework.web.reactive.function.client.ExchangeFunction; 10 | 11 | public class TenantHeaderExchangeFilterFunction implements ExchangeFilterFunction { 12 | private final String headerName; 13 | 14 | public TenantHeaderExchangeFilterFunction() { 15 | this("X-Tenant-Alias"); 16 | } 17 | 18 | public TenantHeaderExchangeFilterFunction(String headerName) { 19 | this.headerName = headerName; 20 | } 21 | 22 | @Override 23 | public Mono filter(ClientRequest clientRequest, ExchangeFunction exchangeFunction) { 24 | clientRequest.headers().set(this.headerName, TenantHolder.getTenant().getName()); 25 | return exchangeFunction.exchange(clientRequest); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/java/sample/issuers/user/MongoUserInitiailizer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Component 10 | class MongoUserInitiailizer implements SmartInitializingSingleton { 11 | private final UserRepository users; 12 | 13 | MongoUserInitiailizer(UserRepository users) { 14 | this.users = users; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | // sha256 w/ salt encoded "password" 20 | String passsword = "73ac8218b92f7494366bf3a03c0c2ee2095d0c03a29cb34c95da327c7aa17173248af74d46ba2d4c"; 21 | 22 | this.users.save(new User(1L, "rob@example.com", passsword, "Rob", "Winch")); 23 | this.users.save(new User(2L, "joe@example.com", passsword, "Joe", "Grandja")); 24 | this.users.save(new User(3L, "josh@example.com", passsword, "Josh", "Cummings")); 25 | this.users.save(new User(4L, "filip@example.com", passsword, "Filip", "Hanik")); 26 | this.users.save(new User(5L, "ria@example.com", passsword, "Ria", "Stein")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/oauth2/TenantClientRegistrationRepository.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.oauth2; 2 | 3 | import sample.multitenancy.Tenant; 4 | import sample.multitenancy.TenantRepository; 5 | 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.security.oauth2.client.registration.ClientRegistration; 8 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; 9 | import org.springframework.security.oauth2.client.registration.ClientRegistrations; 10 | 11 | public class TenantClientRegistrationRepository implements ClientRegistrationRepository { 12 | private final TenantRepository tenantRepository; 13 | 14 | public TenantClientRegistrationRepository(TenantRepository tenantRepository) { 15 | this.tenantRepository = tenantRepository; 16 | } 17 | 18 | @Override 19 | @Cacheable(cacheNames = "client-registrations") 20 | public ClientRegistration findByRegistrationId(String registrationId) { 21 | Tenant tenant = this.tenantRepository.findByAlias(registrationId); 22 | return ClientRegistrations.fromIssuerLocation(tenant.getAttribute("issuerUri")).build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/MongoMessageInitiailizer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Component 10 | class MongoMessageInitiailizer implements SmartInitializingSingleton { 11 | private final MessageRepository messages; 12 | 13 | MongoMessageInitiailizer(MessageRepository messages) { 14 | this.messages = messages; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | String robId = "1"; 20 | String joeId = "2"; 21 | 22 | this.messages.save(new Message(1L, robId, joeId, "Hello World")).block(); 23 | this.messages.save(new Message(2L, robId, joeId,"Greetings Spring Enthusiasts")).block(); 24 | this.messages.save(new Message(3L, robId, joeId,"Hola")).block(); 25 | this.messages.save(new Message(4L, robId, joeId,"Hey Java Devs")).block(); 26 | this.messages.save(new Message(5L, robId, joeId,"Aloha")).block(); 27 | 28 | this.messages.save(new Message(100L, joeId, robId,"Hey Joe")).block(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/java/sample/issuers/webflux/user/MongoUserInitiailizer.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.springframework.beans.factory.SmartInitializingSingleton; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author Rob Winch 8 | */ 9 | @Component 10 | class MongoUserInitiailizer implements SmartInitializingSingleton { 11 | private final UserRepository users; 12 | 13 | MongoUserInitiailizer(UserRepository users) { 14 | this.users = users; 15 | } 16 | 17 | @Override 18 | public void afterSingletonsInstantiated() { 19 | // sha256 w/ salt encoded "password" 20 | String passsword = "73ac8218b92f7494366bf3a03c0c2ee2095d0c03a29cb34c95da327c7aa17173248af74d46ba2d4c"; 21 | 22 | this.users.save(new User(1L, "rob@example.com", passsword, "Rob", "Winch")).block(); 23 | this.users.save(new User(2L, "joe@example.com", passsword, "Joe", "Grandja")).block(); 24 | this.users.save(new User(3L, "josh@example.com", passsword, "Josh", "Cummings")).block(); 25 | this.users.save(new User(4L, "filip@example.com", passsword, "Filip", "Hanik")).block(); 26 | this.users.save(new User(5L, "ria@example.com", passsword, "Ria", "Stein")).block(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/web/WebClientTenantRepository.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.web; 2 | 3 | import java.util.Map; 4 | 5 | import reactor.core.publisher.Mono; 6 | import sample.multitenancy.Tenant; 7 | import sample.multitenancy.TenantRepository; 8 | 9 | import org.springframework.cache.annotation.Cacheable; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | public class WebClientTenantRepository implements TenantRepository { 13 | private final WebClient webClient = WebClient.create(); 14 | 15 | private final String tenantsUrl; 16 | 17 | public WebClientTenantRepository(String tenantsUrl) { 18 | this.tenantsUrl = tenantsUrl; 19 | } 20 | 21 | @Cacheable(cacheNames = "tenants") 22 | public Tenant findByAlias(String alias) { 23 | Mono t = this.webClient.get() 24 | .uri(this.tenantsUrl + "?alias={alias}", alias) 25 | .retrieve() 26 | .bodyToMono(Map.class) 27 | .map(this::fromMap); 28 | return t.block(); 29 | } 30 | 31 | private Tenant fromMap(Map map) { 32 | String alias = (String) map.get("alias"); 33 | String password = (String) map.get("password"); 34 | return new Tenant(alias, password, map); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | @Entity 12 | public class Message { 13 | @Id 14 | private Long id; 15 | 16 | private String to; 17 | 18 | @Column(name="from_address") 19 | private String from; 20 | 21 | private String text; 22 | 23 | public Message() {} 24 | 25 | public Message(Long id, String to, String from, String text) { 26 | this.id = id; 27 | this.to = to; 28 | this.from = from; 29 | this.text = text; 30 | } 31 | 32 | public Long getId() { 33 | return this.id; 34 | } 35 | 36 | public void setId(Long id) { 37 | this.id = id; 38 | } 39 | 40 | public String getTo() { 41 | return this.to; 42 | } 43 | 44 | public void setTo(String to) { 45 | this.to = to; 46 | } 47 | 48 | public String getFrom() { 49 | return this.from; 50 | } 51 | 52 | public void setFrom(String from) { 53 | this.from = from; 54 | } 55 | 56 | public String getText() { 57 | return this.text; 58 | } 59 | 60 | public void setText(String text) { 61 | this.text = text; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.user; 2 | 3 | import javax.validation.Valid; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.validation.BindingResult; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.ModelAttribute; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | 14 | /** 15 | * @author Rob Winch 16 | */ 17 | @Controller 18 | @RequestMapping("/users/") 19 | public class UserController { 20 | private final UserService users; 21 | 22 | public UserController(UserService users) { 23 | this.users = users; 24 | } 25 | 26 | @GetMapping("/signup") 27 | String signupForm(@ModelAttribute User user) { 28 | return "users/form"; 29 | } 30 | 31 | @PostMapping("/signup") 32 | String signup(@Valid User user, BindingResult result) { 33 | if(result.hasErrors()) { 34 | return signupForm(user); 35 | } 36 | return Mono.just(user) 37 | .flatMap(u -> this.users.save(u)) 38 | .then(Mono.just("redirect:/")).block(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.user; 2 | 3 | import javax.validation.Valid; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.validation.BindingResult; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.ModelAttribute; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | 14 | /** 15 | * @author Rob Winch 16 | */ 17 | @Controller 18 | @RequestMapping("/users/") 19 | public class UserController { 20 | private final UserService users; 21 | 22 | public UserController(UserService users) { 23 | this.users = users; 24 | } 25 | 26 | @GetMapping("/signup") 27 | String signupForm(@ModelAttribute User user) { 28 | return "users/form"; 29 | } 30 | 31 | @PostMapping("/signup") 32 | String signup(@Valid User user, BindingResult result) { 33 | if(result.hasErrors()) { 34 | return signupForm(user); 35 | } 36 | return Mono.just(user) 37 | .flatMap(this.users::save) 38 | .then(Mono.just("redirect:/")).block(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.user; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.validation.BindingResult; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.ModelAttribute; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import reactor.core.publisher.Mono; 10 | 11 | import javax.validation.Valid; 12 | 13 | /** 14 | * @author Rob Winch 15 | */ 16 | @Controller 17 | @RequestMapping("/users/") 18 | public class UserController { 19 | private final UserService users; 20 | 21 | public UserController(UserService users) { 22 | this.users = users; 23 | } 24 | 25 | @GetMapping("/signup") 26 | Mono signupForm(@ModelAttribute User user) { 27 | return Mono.just("users/form"); 28 | } 29 | 30 | @PostMapping("/signup") 31 | Mono signup(@Valid User user, BindingResult result) { 32 | if(result.hasErrors()) { 33 | return signupForm(user); 34 | } 35 | return Mono.just(user) 36 | .flatMap(this.users::save) 37 | .then(Mono.just("redirect:/")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.DBRef; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | @Document 11 | public class Message { 12 | @Id 13 | private Long id; 14 | 15 | private String to; 16 | 17 | private String from; 18 | 19 | private String text; 20 | 21 | public Message() {} 22 | 23 | public Message(Long id, String to, String from, String text) { 24 | this.id = id; 25 | this.to = to; 26 | this.from = from; 27 | this.text = text; 28 | } 29 | 30 | public Long getId() { 31 | return this.id; 32 | } 33 | 34 | public void setId(Long id) { 35 | this.id = id; 36 | } 37 | 38 | public String getTo() { 39 | return this.to; 40 | } 41 | 42 | public void setTo(String to) { 43 | this.to = to; 44 | } 45 | 46 | public String getFrom() { 47 | return this.from; 48 | } 49 | 50 | public void setFrom(String from) { 51 | this.from = from; 52 | } 53 | 54 | public String getText() { 55 | return this.text; 56 | } 57 | 58 | public void setText(String text) { 59 | this.text = text; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/Message.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.DBRef; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | /** 8 | * @author Rob Winch 9 | */ 10 | @Document 11 | public class Message { 12 | @Id 13 | private Long id; 14 | 15 | private String to; 16 | 17 | private String from; 18 | 19 | private String text; 20 | 21 | public Message() {} 22 | 23 | public Message(Long id, String to, String from, String text) { 24 | this.id = id; 25 | this.to = to; 26 | this.from = from; 27 | this.text = text; 28 | } 29 | 30 | public Long getId() { 31 | return this.id; 32 | } 33 | 34 | public void setId(Long id) { 35 | this.id = id; 36 | } 37 | 38 | public String getTo() { 39 | return this.to; 40 | } 41 | 42 | public void setTo(String to) { 43 | this.to = to; 44 | } 45 | 46 | public String getFrom() { 47 | return this.from; 48 | } 49 | 50 | public void setFrom(String from) { 51 | this.from = from; 52 | } 53 | 54 | public String getText() { 55 | return this.text; 56 | } 57 | 58 | public void setText(String text) { 59 | this.text = text; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/resources/templates/messages/inbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inbox 6 | 7 | 8 | 9 |
10 |

Inbox

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 |
FromTextView Details
24 | 25 | View 26 | 27 |
No Messages!
34 |
35 | 36 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/resources/templates/messages/inbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inbox 6 | 7 | 8 | 9 |
10 |

Inbox

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 |
FromTextView Details
24 | 25 | View 26 | 27 |
No Messages!
34 |
35 | 36 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/resources/templates/messages/inbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inbox 6 | 7 | 8 | 9 |
10 |

Inbox

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 |
FromTextView Details
24 | 25 | View 26 | 27 |
No Messages!
34 |
35 | 36 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/user/WebClientUserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.user; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Component 13 | public class WebClientUserService implements UserService { 14 | private final WebClient webClient; 15 | 16 | private final String usersUrl; 17 | 18 | public WebClientUserService(WebClient webClient, 19 | @Value("${users-url}") String usersUrl) { 20 | this.webClient = webClient; 21 | this.usersUrl = usersUrl; 22 | } 23 | 24 | public Mono save(User user) { 25 | return this.webClient.post() 26 | .uri(this.usersUrl) 27 | .syncBody(user) 28 | .retrieve() 29 | .bodyToMono(User.class); 30 | } 31 | 32 | public Mono findByEmail(String email) { 33 | return this.webClient.get() 34 | .uri(this.usersUrl + "/?email={email}", email) 35 | .retrieve() 36 | .bodyToMono(User.class); 37 | } 38 | 39 | public Mono findById(String id) { 40 | return this.webClient.get() 41 | .uri(this.usersUrl + "/{id}", id) 42 | .retrieve() 43 | .bodyToMono(User.class); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/user/WebClientUserService.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.user; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Component 13 | public class WebClientUserService implements UserService { 14 | private final WebClient webClient; 15 | 16 | private final String usersUrl; 17 | 18 | public WebClientUserService(WebClient webClient, 19 | @Value("${users-url}") String usersUrl) { 20 | this.webClient = webClient; 21 | this.usersUrl = usersUrl; 22 | } 23 | 24 | public Mono save(User user) { 25 | return this.webClient.post() 26 | .uri(this.usersUrl) 27 | .syncBody(user) 28 | .retrieve() 29 | .bodyToMono(User.class); 30 | } 31 | 32 | public Mono findByEmail(String email) { 33 | return this.webClient.get() 34 | .uri(this.usersUrl + "/?email={email}", email) 35 | .retrieve() 36 | .bodyToMono(User.class); 37 | } 38 | 39 | public Mono findById(String id) { 40 | return this.webClient.get() 41 | .uri(this.usersUrl + "/{id}", id) 42 | .retrieve() 43 | .bodyToMono(User.class); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/InboxApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; 7 | import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; 8 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | 11 | @SpringBootApplication 12 | public class InboxApplication { 13 | 14 | @Bean 15 | WebClient webClient(ClientRegistrationRepository clientRegistrations, 16 | OAuth2AuthorizedClientRepository authorizedClients) { 17 | ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 18 | new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); 19 | oauth2.setDefaultOAuth2AuthorizedClient(true); 20 | return WebClient.builder() 21 | .apply(oauth2.oauth2Configuration()) 22 | .build(); 23 | } 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(InboxApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.security.access.prepost.PostAuthorize; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @RestController 16 | @RequestMapping("/messages") 17 | public class MessageController { 18 | private final MessageRepository messages; 19 | 20 | public MessageController(MessageRepository messages) { 21 | this.messages = messages; 22 | } 23 | 24 | @GetMapping("/inbox") 25 | Iterable inbox(@CurrentUserId String currentUserId) { 26 | return this.messages.findByTo(currentUserId); 27 | } 28 | 29 | @GetMapping("/{id}") 30 | @PostAuthorize("returnObject?.to == authentication?.tokenAttributes['user_id']") 31 | Optional findById(@PathVariable Long id) { 32 | return this.messages.findById(id); 33 | } 34 | 35 | @DeleteMapping("/{id}") 36 | void deleteById(@PathVariable Long id) { 37 | this.messages.deleteById(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.security.access.prepost.PostAuthorize; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @RestController 16 | @RequestMapping("/messages") 17 | public class MessageController { 18 | private final MessageRepository messages; 19 | 20 | public MessageController(MessageRepository messages) { 21 | this.messages = messages; 22 | } 23 | 24 | @GetMapping("/inbox") 25 | Iterable inbox(@CurrentUserId String currentUserId) { 26 | return this.messages.findByTo(currentUserId); 27 | } 28 | 29 | @GetMapping("/{id}") 30 | @PostAuthorize("returnObject?.to == authentication?.tokenAttributes['user_id']") 31 | Optional findById(@PathVariable Long id) { 32 | return this.messages.findById(id); 33 | } 34 | 35 | @DeleteMapping("/{id}") 36 | void deleteById(@PathVariable Long id) { 37 | this.messages.deleteById(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/user/WebClientUserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.user; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Mono; 7 | 8 | /** 9 | * @author Rob Winch 10 | */ 11 | @Component 12 | public class WebClientUserService implements UserService { 13 | private final WebClient webClient; 14 | 15 | private final String usersUrl; 16 | 17 | public WebClientUserService(WebClient webClient, 18 | @Value("${users-url}") String usersUrl) { 19 | this.webClient = webClient; 20 | this.usersUrl = usersUrl; 21 | } 22 | 23 | public Mono save(User user) { 24 | return this.webClient.post() 25 | .uri(this.usersUrl) 26 | .syncBody(user) 27 | .retrieve() 28 | .bodyToMono(User.class); 29 | } 30 | 31 | public Mono findByEmail(String email) { 32 | return this.webClient.get() 33 | .uri(this.usersUrl + "/?email={email}", email) 34 | .retrieve() 35 | .bodyToMono(User.class); 36 | } 37 | 38 | public Mono findById(String id) { 39 | return this.webClient.get() 40 | .uri(this.usersUrl + "/{id}", id) 41 | .retrieve() 42 | .bodyToMono(User.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/InboxApplication.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 7 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction; 8 | import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | 11 | @SpringBootApplication 12 | public class InboxApplication { 13 | 14 | @Bean 15 | WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, 16 | ServerOAuth2AuthorizedClientRepository authorizedClients) { 17 | ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 18 | new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); 19 | oauth2.setDefaultOAuth2AuthorizedClient(true); 20 | return WebClient.builder() 21 | .filter(oauth2) 22 | .build(); 23 | } 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(InboxApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/message/src/main/java/sample/issuers/webflux/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.message; 2 | 3 | import org.springframework.security.access.prepost.PostAuthorize; 4 | import org.springframework.web.bind.annotation.DeleteMapping; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @RestController 16 | @RequestMapping("/messages") 17 | public class MessageController { 18 | private final MessageRepository messages; 19 | 20 | public MessageController(MessageRepository messages) { 21 | this.messages = messages; 22 | } 23 | 24 | @GetMapping("/inbox") 25 | Flux inbox(@CurrentUserId String currentUserId) { 26 | return this.messages.findByTo(currentUserId); 27 | } 28 | 29 | @GetMapping("/{id}") 30 | @PostAuthorize("returnObject?.to == authentication?.tokenAttributes['user_id']") 31 | Mono findById(@PathVariable Long id) { 32 | return this.messages.findById(id); 33 | } 34 | 35 | @DeleteMapping("/{id}") 36 | Mono deleteById(@PathVariable Long id) { 37 | return this.messages.deleteById(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.message; 2 | 3 | import java.util.Collections; 4 | 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @Controller 16 | @RequestMapping("/messages") 17 | public class MessageController { 18 | private final MessageService messages; 19 | 20 | public MessageController(MessageService messages) { 21 | this.messages = messages; 22 | } 23 | 24 | @GetMapping("/inbox") 25 | ModelAndView inbox() { 26 | return new ModelAndView("messages/inbox", 27 | Collections.singletonMap("messages", this.messages.inbox().collectList().block())); 28 | } 29 | 30 | @GetMapping("/{id}") 31 | ModelAndView message(@PathVariable String id) { 32 | return new ModelAndView("messages/view", 33 | Collections.singletonMap("message", this.messages.findById(id).block())); 34 | } 35 | 36 | @DeleteMapping("/{id}") 37 | String deleteById(@PathVariable String id) { 38 | this.messages.deleteById(id).block(); 39 | return "redirect:/messages/inbox?deleted"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.message; 2 | 3 | import java.util.Collections; 4 | 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @Controller 16 | @RequestMapping("/messages") 17 | public class MessageController { 18 | private final MessageService messages; 19 | 20 | public MessageController(MessageService messages) { 21 | this.messages = messages; 22 | } 23 | 24 | @GetMapping("/inbox") 25 | ModelAndView inbox() { 26 | return new ModelAndView("messages/inbox", 27 | Collections.singletonMap("messages", this.messages.inbox().collectList().block())); 28 | } 29 | 30 | @GetMapping("/{id}") 31 | ModelAndView message(@PathVariable String id) { 32 | return new ModelAndView("messages/view", 33 | Collections.singletonMap("message", this.messages.findById(id).block())); 34 | } 35 | 36 | @DeleteMapping("/{id}") 37 | String deleteById(@PathVariable String id) { 38 | this.messages.deleteById(id).block(); 39 | return "redirect:/messages/inbox?deleted"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.message; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.DeleteMapping; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.reactive.result.view.Rendering; 9 | import reactor.core.publisher.Mono; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Controller 15 | @RequestMapping("/messages") 16 | public class MessageController { 17 | private final MessageService messages; 18 | 19 | public MessageController(MessageService messages) { 20 | this.messages = messages; 21 | } 22 | 23 | @GetMapping("/inbox") 24 | Rendering inbox() { 25 | return Rendering.view("messages/inbox") 26 | .modelAttribute("messages", this.messages.inbox()) 27 | .build(); 28 | } 29 | 30 | @GetMapping("/{id}") 31 | Rendering message(@PathVariable String id) { 32 | return Rendering.view("messages/view") 33 | .modelAttribute("message", this.messages.findById(id)) 34 | .build(); 35 | } 36 | 37 | @DeleteMapping("/{id}") 38 | Mono deleteById(@PathVariable String id) { 39 | return this.messages 40 | .deleteById(id) 41 | .thenReturn("redirect:/messages/inbox?deleted"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/UserInitializer.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import javax.transaction.Transactional; 4 | 5 | import sample.multitenancy.TenantHolder; 6 | import sample.multitenancy.Tenant; 7 | 8 | import org.springframework.beans.factory.SmartInitializingSingleton; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Component 15 | class UserInitializer implements SmartInitializingSingleton { 16 | private final UserRepository users; 17 | 18 | UserInitializer(UserRepository users) { 19 | this.users = users; 20 | } 21 | 22 | @Override 23 | @Transactional 24 | public void afterSingletonsInstantiated() { 25 | // sha256 w/ salt encoded "password" 26 | String password = "73ac8218b92f7494366bf3a03c0c2ee2095d0c03a29cb34c95da327c7aa17173248af74d46ba2d4c"; 27 | 28 | TenantHolder.setTenant(new Tenant("widgets")); 29 | this.users.save(new User(1L, "rob@example.com", password, "Rob", "Winch", "rob", "widgets")); 30 | this.users.save(new User(2L, "joe@example.com", password, "Joe", "Grandja", "joe", "widgets")); 31 | 32 | TenantHolder.setTenant(new Tenant("socks")); 33 | this.users.save(new User(3L, "josh@example.com", password, "Josh", "Cummings", "josh", "socks")); 34 | 35 | TenantHolder.setTenant(new Tenant("toasters")); 36 | this.users.save(new User(4L, "filip@example.com", password, "Filip", "Hanik", "filip", "toasters")); 37 | this.users.save(new User(5L, "ria@example.com", password, "Ria", "Stein", "ria", "toasters")); 38 | 39 | TenantHolder.clearTenant(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/web/TenantResolverFilter.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.web; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.FilterChain; 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import sample.multitenancy.Tenant; 10 | import sample.multitenancy.TenantHolder; 11 | import sample.multitenancy.TenantRepository; 12 | 13 | import org.springframework.core.convert.converter.Converter; 14 | import org.springframework.web.filter.OncePerRequestFilter; 15 | 16 | public class TenantResolverFilter extends OncePerRequestFilter { 17 | private Converter tenantIdentifierConverter = new HeaderTenantIdentifierResolver(); 18 | private final TenantRepository tenantRepository; 19 | 20 | public TenantResolverFilter(TenantRepository tenantRepository) { 21 | this.tenantRepository = tenantRepository; 22 | } 23 | 24 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 25 | throws ServletException, IOException { 26 | 27 | String alias = this.tenantIdentifierConverter.convert(request); 28 | 29 | try { 30 | Tenant tenant = this.tenantRepository.findByAlias(alias); 31 | TenantHolder.setTenant(tenant); 32 | filterChain.doFilter(request, response); 33 | } finally { 34 | TenantHolder.clearTenant(); 35 | } 36 | } 37 | 38 | public void setTenantIdentifierConverter(Converter tenantIdentifierConverter) { 39 | this.tenantIdentifierConverter = tenantIdentifierConverter; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/java/sample/issuers/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Optional; 5 | import java.util.Random; 6 | import javax.validation.Valid; 7 | 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | /** 18 | * @author Rob Winch 19 | */ 20 | @RestController 21 | @RequestMapping(path="/users", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 22 | public class UserController { 23 | private final Random random = new SecureRandom(); 24 | 25 | private final UserRepository users; 26 | 27 | public UserController(UserRepository users) { 28 | this.users = users; 29 | } 30 | 31 | @GetMapping 32 | Iterable users() { 33 | return this.users.findAll(); 34 | } 35 | 36 | @GetMapping("/{id}") 37 | Optional findById(@PathVariable Long id) { 38 | return this.users.findById(id); 39 | } 40 | 41 | @GetMapping(params = "email") 42 | User findByEmail(@RequestParam String email) { 43 | return this.users.findByEmail(email); 44 | } 45 | 46 | @PostMapping 47 | User save(@Valid @RequestBody User user) { 48 | if (user.getId() == null) { 49 | user.setId(this.random.nextLong()); 50 | } 51 | return this.users.save(user); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Optional; 5 | import java.util.Random; 6 | import javax.validation.Valid; 7 | 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | /** 18 | * @author Rob Winch 19 | */ 20 | @RestController 21 | @RequestMapping(path="/users", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 22 | public class UserController { 23 | private final Random random = new SecureRandom(); 24 | 25 | private final UserRepository users; 26 | 27 | public UserController(UserRepository users) { 28 | this.users = users; 29 | } 30 | 31 | @GetMapping 32 | Iterable users() { 33 | return this.users.findAll(); 34 | } 35 | 36 | @GetMapping("/{id}") 37 | Optional findById(@PathVariable Long id) { 38 | return this.users.findById(id); 39 | } 40 | 41 | @GetMapping(params = "email") 42 | User findByEmail(@RequestParam String email) { 43 | return this.users.findByEmail(email); 44 | } 45 | 46 | @PostMapping 47 | User save(@Valid @RequestBody User user) { 48 | if (user.getId() == null) { 49 | user.setId(this.random.nextLong()); 50 | } 51 | return this.users.save(user); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.user; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | public class User { 7 | private Long id; 8 | 9 | private String email; 10 | 11 | private String password; 12 | 13 | private String firstName; 14 | 15 | private String lastName; 16 | 17 | private String alias; 18 | 19 | public User() {} 20 | 21 | public User(User user) { 22 | this(user.getId(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); 23 | this.alias = user.getAlias(); 24 | } 25 | 26 | public User(Long id, String email, String password, String firstName, 27 | String lastName) { 28 | this.id = id; 29 | this.email = email; 30 | this.password = password; 31 | this.firstName = firstName; 32 | this.lastName = lastName; 33 | } 34 | 35 | public Long getId() { 36 | return this.id; 37 | } 38 | 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getEmail() { 44 | return this.email; 45 | } 46 | 47 | public void setEmail(String email) { 48 | this.email = email; 49 | } 50 | 51 | public String getPassword() { 52 | return this.password; 53 | } 54 | 55 | public void setPassword(String password) { 56 | this.password = password; 57 | } 58 | 59 | public String getFirstName() { 60 | return this.firstName; 61 | } 62 | 63 | public void setFirstName(String firstName) { 64 | this.firstName = firstName; 65 | } 66 | 67 | public String getLastName() { 68 | return this.lastName; 69 | } 70 | 71 | public void setLastName(String lastName) { 72 | this.lastName = lastName; 73 | } 74 | 75 | public String getAlias() { 76 | return this.alias; 77 | } 78 | 79 | public void setAlias(String alias) { 80 | this.alias = alias; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /multiple-issuers/gateway/src/main/java/sample/issuers/gateway/IdFilter.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.gateway; 2 | 3 | import org.springframework.security.oauth2.jwt.Jwt; 4 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.server.ServerWebExchange; 7 | import org.springframework.web.server.WebFilter; 8 | import org.springframework.web.server.WebFilterChain; 9 | import reactor.core.publisher.Mono; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Component 15 | public class IdFilter implements WebFilter { 16 | 17 | public static final String USER_ID_HEADER_NAME = "user-id"; 18 | 19 | public static final String USER_ID_CLAIM_NAME = "user_id"; 20 | 21 | @Override 22 | public Mono filter(ServerWebExchange exchange, 23 | WebFilterChain chain) { 24 | firewall(exchange); 25 | return exchange.getPrincipal() 26 | .cast(JwtAuthenticationToken.class) 27 | .map(JwtAuthenticationToken::getToken) 28 | .map(Jwt::getClaims) 29 | .filter(claims -> claims.containsKey(USER_ID_CLAIM_NAME)) 30 | .map(claims -> claims.get(USER_ID_CLAIM_NAME)) 31 | .cast(String.class) 32 | .map(userId -> withUserId(exchange, userId)) 33 | .defaultIfEmpty(exchange) 34 | .flatMap(chain::filter); 35 | } 36 | 37 | private void firewall(ServerWebExchange exchange) { 38 | if (exchange.getRequest().getHeaders().containsKey(USER_ID_HEADER_NAME)) { 39 | throw new IllegalStateException("Malicious Client is trying to submit user-id header"); 40 | } 41 | } 42 | 43 | private ServerWebExchange withUserId(ServerWebExchange exchange, String userId) { 44 | return exchange.mutate() 45 | .request(r -> r.header(USER_ID_HEADER_NAME, userId)) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tenant-domain-object/gateway/src/main/java/sample/tenants/gateway/IdFilter.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.gateway; 2 | 3 | import org.springframework.security.oauth2.jwt.Jwt; 4 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.server.ServerWebExchange; 7 | import org.springframework.web.server.WebFilter; 8 | import org.springframework.web.server.WebFilterChain; 9 | import reactor.core.publisher.Mono; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Component 15 | public class IdFilter implements WebFilter { 16 | 17 | public static final String USER_ID_HEADER_NAME = "user-id"; 18 | 19 | public static final String USER_ID_CLAIM_NAME = "user_id"; 20 | 21 | @Override 22 | public Mono filter(ServerWebExchange exchange, 23 | WebFilterChain chain) { 24 | firewall(exchange); 25 | return exchange.getPrincipal() 26 | .cast(JwtAuthenticationToken.class) 27 | .map(JwtAuthenticationToken::getToken) 28 | .map(Jwt::getClaims) 29 | .filter(claims -> claims.containsKey(USER_ID_CLAIM_NAME)) 30 | .map(claims -> claims.get(USER_ID_CLAIM_NAME)) 31 | .cast(String.class) 32 | .map(userId -> withUserId(exchange, userId)) 33 | .defaultIfEmpty(exchange) 34 | .flatMap(chain::filter); 35 | } 36 | 37 | private void firewall(ServerWebExchange exchange) { 38 | if (exchange.getRequest().getHeaders().containsKey(USER_ID_HEADER_NAME)) { 39 | throw new IllegalStateException("Malicious Client is trying to submit user-id header"); 40 | } 41 | } 42 | 43 | private ServerWebExchange withUserId(ServerWebExchange exchange, String userId) { 44 | return exchange.mutate() 45 | .request(r -> r.header(USER_ID_HEADER_NAME, userId)) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.user; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | public class User { 7 | private Long id; 8 | 9 | private String email; 10 | 11 | private String password; 12 | 13 | private String firstName; 14 | 15 | private String lastName; 16 | 17 | private String alias; 18 | 19 | public User() {} 20 | 21 | public User(User user) { 22 | this(user.getId(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); 23 | this.alias = user.getAlias(); 24 | } 25 | 26 | public User(Long id, String email, String password, String firstName, 27 | String lastName) { 28 | this.id = id; 29 | this.email = email; 30 | this.password = password; 31 | this.firstName = firstName; 32 | this.lastName = lastName; 33 | } 34 | 35 | public Long getId() { 36 | return this.id; 37 | } 38 | 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getEmail() { 44 | return this.email; 45 | } 46 | 47 | public void setEmail(String email) { 48 | this.email = email; 49 | } 50 | 51 | public String getPassword() { 52 | return this.password; 53 | } 54 | 55 | public void setPassword(String password) { 56 | this.password = password; 57 | } 58 | 59 | public String getFirstName() { 60 | return this.firstName; 61 | } 62 | 63 | public void setFirstName(String firstName) { 64 | this.firstName = firstName; 65 | } 66 | 67 | public String getLastName() { 68 | return this.lastName; 69 | } 70 | 71 | public void setLastName(String lastName) { 72 | this.lastName = lastName; 73 | } 74 | 75 | public String getAlias() { 76 | return this.alias; 77 | } 78 | 79 | public void setAlias(String alias) { 80 | this.alias = alias; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/gateway/src/main/java/sample/issuers/webflux/gateway/IdFilter.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.gateway; 2 | 3 | import org.springframework.security.oauth2.jwt.Jwt; 4 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.server.ServerWebExchange; 7 | import org.springframework.web.server.WebFilter; 8 | import org.springframework.web.server.WebFilterChain; 9 | import reactor.core.publisher.Mono; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Component 15 | public class IdFilter implements WebFilter { 16 | 17 | public static final String USER_ID_HEADER_NAME = "user-id"; 18 | 19 | public static final String USER_ID_CLAIM_NAME = "user_id"; 20 | 21 | @Override 22 | public Mono filter(ServerWebExchange exchange, 23 | WebFilterChain chain) { 24 | firewall(exchange); 25 | return exchange.getPrincipal() 26 | .cast(JwtAuthenticationToken.class) 27 | .map(JwtAuthenticationToken::getToken) 28 | .map(Jwt::getClaims) 29 | .filter(claims -> claims.containsKey(USER_ID_CLAIM_NAME)) 30 | .map(claims -> claims.get(USER_ID_CLAIM_NAME)) 31 | .cast(String.class) 32 | .map(userId -> withUserId(exchange, userId)) 33 | .defaultIfEmpty(exchange) 34 | .flatMap(chain::filter); 35 | } 36 | 37 | private void firewall(ServerWebExchange exchange) { 38 | if (exchange.getRequest().getHeaders().containsKey(USER_ID_HEADER_NAME)) { 39 | throw new IllegalStateException("Malicious Client is trying to submit user-id header"); 40 | } 41 | } 42 | 43 | private ServerWebExchange withUserId(ServerWebExchange exchange, String userId) { 44 | return exchange.mutate() 45 | .request(r -> r.header(USER_ID_HEADER_NAME, userId)) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.user; 2 | 3 | /** 4 | * @author Rob Winch 5 | */ 6 | public class User { 7 | private Long id; 8 | 9 | private String email; 10 | 11 | private String password; 12 | 13 | private String firstName; 14 | 15 | private String lastName; 16 | 17 | private String alias; 18 | 19 | public User() {} 20 | 21 | public User(User user) { 22 | this(user.getId(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); 23 | this.alias = user.getAlias(); 24 | } 25 | 26 | public User(Long id, String email, String password, String firstName, 27 | String lastName) { 28 | this.id = id; 29 | this.email = email; 30 | this.password = password; 31 | this.firstName = firstName; 32 | this.lastName = lastName; 33 | } 34 | 35 | public Long getId() { 36 | return this.id; 37 | } 38 | 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getEmail() { 44 | return this.email; 45 | } 46 | 47 | public void setEmail(String email) { 48 | this.email = email; 49 | } 50 | 51 | public String getPassword() { 52 | return this.password; 53 | } 54 | 55 | public void setPassword(String password) { 56 | this.password = password; 57 | } 58 | 59 | public String getFirstName() { 60 | return this.firstName; 61 | } 62 | 63 | public void setFirstName(String firstName) { 64 | this.firstName = firstName; 65 | } 66 | 67 | public String getLastName() { 68 | return this.lastName; 69 | } 70 | 71 | public void setLastName(String lastName) { 72 | this.lastName = lastName; 73 | } 74 | 75 | public String getAlias() { 76 | return this.alias; 77 | } 78 | 79 | public void setAlias(String alias) { 80 | this.alias = alias; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample 8 | multi-tenancy-demos 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 1.8 16 | 1.8 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-dependencies 24 | 2.2.0.M6 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | multi-tenancy 33 | multiple-issuers 34 | multiple-issuers-webflux 35 | tenant-domain-object 36 | 37 | 38 | 39 | 40 | spring-snapshots 41 | Spring Snapshots 42 | https://repo.spring.io/snapshot 43 | 44 | true 45 | 46 | 47 | 48 | spring-milestones 49 | Spring Milestones 50 | https://repo.spring.io/milestone 51 | 52 | false 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/java/sample/issuers/webflux/user/UserController.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | import reactor.core.scheduler.Schedulers; 14 | 15 | import javax.validation.Valid; 16 | import java.security.SecureRandom; 17 | import java.util.Random; 18 | 19 | /** 20 | * @author Rob Winch 21 | */ 22 | @RestController 23 | @RequestMapping(path="/users", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 24 | public class UserController { 25 | private final Random random = new SecureRandom(); 26 | 27 | private final UserRepository users; 28 | 29 | public UserController(UserRepository users) { 30 | this.users = users; 31 | } 32 | 33 | @GetMapping 34 | Flux users() { 35 | return this.users.findAll(); 36 | } 37 | 38 | @GetMapping("/{id}") 39 | Mono findById(@PathVariable Long id) { 40 | return this.users.findById(id); 41 | } 42 | 43 | @GetMapping(params = "email") 44 | Mono findByEmail(@RequestParam String email) { 45 | return this.users.findByEmail(email); 46 | } 47 | 48 | @PostMapping 49 | Mono save(@Valid @RequestBody User user) { 50 | return Mono.justOrEmpty(user) 51 | .doOnSuccess(u -> { 52 | if (u.getId() == null) { 53 | u.setId(this.random.nextLong()); 54 | } 55 | }) 56 | .publishOn(Schedulers.parallel()) 57 | .flatMap(this.users::save); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/message/WebClientMessageService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.message; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | import sample.issuers.inbox.user.User; 6 | import sample.issuers.inbox.user.UserService; 7 | 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @Component 16 | public class WebClientMessageService implements MessageService { 17 | private final WebClient webClient; 18 | 19 | private final String messagesUrl; 20 | 21 | private final UserService users; 22 | 23 | public WebClientMessageService(WebClient webClient, 24 | @Value("${messages-url}") String messagesUrl, UserService users) { 25 | this.webClient = webClient; 26 | this.messagesUrl = messagesUrl; 27 | this.users = users; 28 | } 29 | 30 | @Override 31 | public Flux inbox() { 32 | return this.webClient.get() 33 | .uri(this.messagesUrl + "/inbox") 34 | .retrieve() 35 | .bodyToFlux(MessageDto.class) 36 | .flatMap(this::toMessage); 37 | } 38 | 39 | @Override 40 | public Mono findById(String id) { 41 | return this.webClient.get() 42 | .uri(this.messagesUrl + "/{id}", id) 43 | .retrieve() 44 | .bodyToMono(MessageDto.class) 45 | .flatMap(this::toMessage); 46 | } 47 | 48 | @Override 49 | public Mono deleteById(String id) { 50 | return this.webClient.delete() 51 | .uri(this.messagesUrl + "/{id}", id) 52 | .exchange() 53 | .then(Mono.empty()); 54 | } 55 | 56 | private Mono toMessage(MessageDto dto) { 57 | Mono to = this.users.findById(dto.getTo()); 58 | Mono from = this.users.findById(dto.getFrom()); 59 | return Mono.zip(to, from) 60 | .map(tuple -> new Message(dto.getId(), tuple.getT1(), tuple.getT2(), dto.getText())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/message/WebClientMessageService.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.message; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | import sample.tenants.inbox.user.User; 6 | import sample.tenants.inbox.user.UserService; 7 | 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | /** 13 | * @author Rob Winch 14 | */ 15 | @Component 16 | public class WebClientMessageService implements MessageService { 17 | private final WebClient webClient; 18 | 19 | private final String messagesUrl; 20 | 21 | private final UserService users; 22 | 23 | public WebClientMessageService(WebClient webClient, 24 | @Value("${messages-url}") String messagesUrl, UserService users) { 25 | this.webClient = webClient; 26 | this.messagesUrl = messagesUrl; 27 | this.users = users; 28 | } 29 | 30 | @Override 31 | public Flux inbox() { 32 | return this.webClient.get() 33 | .uri(this.messagesUrl + "/inbox") 34 | .retrieve() 35 | .bodyToFlux(MessageDto.class) 36 | .flatMap(this::toMessage); 37 | } 38 | 39 | @Override 40 | public Mono findById(String id) { 41 | return this.webClient.get() 42 | .uri(this.messagesUrl + "/{id}", id) 43 | .retrieve() 44 | .bodyToMono(MessageDto.class) 45 | .flatMap(this::toMessage); 46 | } 47 | 48 | @Override 49 | public Mono deleteById(String id) { 50 | return this.webClient.delete() 51 | .uri(this.messagesUrl + "/{id}", id) 52 | .exchange() 53 | .then(Mono.empty()); 54 | } 55 | 56 | private Mono toMessage(MessageDto dto) { 57 | Mono to = this.users.findById(dto.getTo()); 58 | Mono from = this.users.findById(dto.getFrom()); 59 | return Mono.zip(to, from) 60 | .map(tuple -> new Message(dto.getId(), tuple.getT1(), tuple.getT2(), dto.getText())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/message/WebClientMessageService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.message; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | import sample.issuers.webflux.inbox.user.User; 9 | import sample.issuers.webflux.inbox.user.UserService; 10 | 11 | /** 12 | * @author Rob Winch 13 | */ 14 | @Component 15 | public class WebClientMessageService implements MessageService { 16 | private final WebClient webClient; 17 | 18 | private final String messagesUrl; 19 | 20 | private final UserService users; 21 | 22 | public WebClientMessageService(WebClient webClient, 23 | @Value("${messages-url}") String messagesUrl, UserService users) { 24 | this.webClient = webClient; 25 | this.messagesUrl = messagesUrl; 26 | this.users = users; 27 | } 28 | 29 | @Override 30 | public Flux inbox() { 31 | return this.webClient.get() 32 | .uri(this.messagesUrl + "/inbox") 33 | .retrieve() 34 | .bodyToFlux(MessageDto.class) 35 | .flatMap(this::toMessage); 36 | } 37 | 38 | @Override 39 | public Mono findById(String id) { 40 | return this.webClient.get() 41 | .uri(this.messagesUrl + "/{id}", id) 42 | .retrieve() 43 | .bodyToMono(MessageDto.class) 44 | .flatMap(this::toMessage); 45 | } 46 | 47 | @Override 48 | public Mono deleteById(String id) { 49 | return this.webClient.delete() 50 | .uri(this.messagesUrl + "/{id}", id) 51 | .exchange() 52 | .then(Mono.empty()); 53 | } 54 | 55 | private Mono toMessage(MessageDto dto) { 56 | Mono to = this.users.findById(dto.getTo()); 57 | Mono from = this.users.findById(dto.getFrom()); 58 | return Mono.zip(to, from) 59 | .map(t2 -> new Message(dto.getId(), t2.getT1(), t2.getT2(), dto.getText())); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/Tenant.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy; 2 | 3 | import java.security.Principal; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | 7 | /** 8 | * @author Josh Cummings 9 | */ 10 | public class Tenant implements Principal { 11 | public static final Tenant DEFAULT_TENANT = new Tenant("public"); 12 | 13 | private Object principal; 14 | 15 | private Object credentials; 16 | 17 | private Map attributes; 18 | 19 | public Tenant(Object principal) { 20 | this.principal = principal; 21 | } 22 | 23 | public Tenant(Object principal, Object credentials, Map attributes) { 24 | this.principal = principal; 25 | this.credentials = credentials; 26 | this.attributes = attributes; 27 | } 28 | 29 | public String getName() { 30 | return this.principal.toString(); 31 | } 32 | 33 | public Object getPrincipal() { 34 | return this.principal; 35 | } 36 | 37 | public void setPrincipal(Object principal) { 38 | this.principal = principal; 39 | } 40 | 41 | public Object getCredentials() { 42 | return this.credentials; 43 | } 44 | 45 | public void setCredentials(Object credentials) { 46 | this.credentials = credentials; 47 | } 48 | 49 | public A getAttribute(String attribute) { 50 | return (A) this.attributes.get(attribute); 51 | } 52 | 53 | public Map getAttributes() { 54 | return this.attributes; 55 | } 56 | 57 | public void setAttributes(Map attributes) { 58 | this.attributes = attributes; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return this.principal.toString(); 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) return true; 69 | if (o == null || getClass() != o.getClass()) return false; 70 | Tenant tenantDto = (Tenant) o; 71 | return Objects.equals(this.principal, tenantDto.principal); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return Objects.hash(this.principal); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /multi-tenancy/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | multi-tenancy 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | 12 | sample 13 | multi-tenancy-demos 14 | 0.0.1-SNAPSHOT 15 | 16 | 17 | multi-tenancy 18 | A sample multi-tenancy model 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-aop 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-security 28 | 29 | 30 | org.springframework.security 31 | spring-security-oauth2-client 32 | true 33 | 34 | 35 | org.springframework 36 | spring-context 37 | 38 | 39 | org.springframework 40 | spring-web 41 | 42 | 43 | org.springframework 44 | spring-webflux 45 | 46 | 47 | 48 | javax.servlet 49 | javax.servlet-api 50 | 51 | 52 | io.projectreactor.netty 53 | reactor-netty 54 | 55 | 56 | org.hibernate 57 | hibernate-core 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/MessageInitializer.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import sample.multitenancy.Tenant; 4 | import sample.multitenancy.TenantHolder; 5 | 6 | import org.springframework.beans.factory.SmartInitializingSingleton; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.transaction.support.TransactionTemplate; 9 | 10 | /** 11 | * @author Rob Winch 12 | */ 13 | @Component 14 | class MessageInitializer implements SmartInitializingSingleton { 15 | private final MessageRepository messages; 16 | private final TransactionTemplate transaction; 17 | 18 | MessageInitializer(MessageRepository messages, TransactionTemplate transaction) { 19 | this.messages = messages; 20 | this.transaction = transaction; 21 | } 22 | 23 | @Override 24 | public void afterSingletonsInstantiated() { 25 | String robId = "1"; 26 | String joeId = "2"; 27 | String joshId = "3"; 28 | 29 | TenantHolder.setTenant(new Tenant("widgets")); 30 | this.transaction.execute(status -> { 31 | this.messages.save(new Message(1L, robId, joeId, "Hello World")); 32 | this.messages.save(new Message(2L, robId, joeId, "Greetings Spring Enthusiasts")); 33 | this.messages.save(new Message(3L, robId, joeId, "Hola")); 34 | this.messages.save(new Message(4L, robId, joeId, "Hey Java Devs")); 35 | this.messages.save(new Message(5L, robId, joeId, "Aloha")); 36 | this.messages.save(new Message(6L, joshId, robId, "Welcome to Spring")); 37 | return null; 38 | }); 39 | 40 | TenantHolder.setTenant(new Tenant("widgets")); 41 | this.transaction.execute(status -> { 42 | this.messages.save(new Message(100L, joeId, robId, "Hey Joe")); 43 | this.messages.save(new Message(101L, joeId, joshId, "Ora Viva")); 44 | return null; 45 | }); 46 | 47 | TenantHolder.setTenant(new Tenant("socks")); 48 | this.transaction.execute(status -> { 49 | this.messages.save(new Message(1000L, joshId, robId, "Welcome to Spring")); 50 | return null; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/InboxApplication.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox; 2 | 3 | import sample.multitenancy.TenantRepository; 4 | import sample.multitenancy.oauth2.TenantClientRegistrationRepository; 5 | import sample.multitenancy.web.TenantHeaderExchangeFilterFunction; 6 | import sample.multitenancy.web.WebClientTenantRepository; 7 | 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; 13 | import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; 14 | import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; 15 | import org.springframework.web.reactive.function.client.WebClient; 16 | 17 | @SpringBootApplication 18 | public class InboxApplication { 19 | 20 | @Value("${tenants-url}") String tenantsUrl; 21 | 22 | @Bean 23 | TenantRepository tenantRepository() { 24 | return new WebClientTenantRepository(this.tenantsUrl); 25 | } 26 | 27 | @Bean 28 | ClientRegistrationRepository clientRegistrationRepository(TenantRepository tenantRepository) { 29 | return new TenantClientRegistrationRepository(tenantRepository); 30 | } 31 | 32 | @Bean 33 | WebClient webClient(ClientRegistrationRepository clientRegistrations, 34 | OAuth2AuthorizedClientRepository authorizedClients) { 35 | ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 36 | new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); 37 | oauth2.setDefaultOAuth2AuthorizedClient(true); 38 | 39 | return WebClient.builder() 40 | .filter(new TenantHeaderExchangeFilterFunction()) 41 | .apply(oauth2.oauth2Configuration()) 42 | .build(); 43 | } 44 | 45 | public static void main(String[] args) { 46 | SpringApplication.run(InboxApplication.class, args); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /multiple-issuers/user/src/main/java/sample/issuers/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.user; 2 | 3 | import javax.validation.constraints.NotEmpty; 4 | 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.mongodb.core.index.Indexed; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Document 13 | public class User { 14 | @Id 15 | private Long id; 16 | 17 | @Indexed 18 | @NotEmpty(message = "This field is required") 19 | private String email; 20 | 21 | private String password; 22 | 23 | @NotEmpty(message = "This field is required") 24 | private String firstName; 25 | 26 | @NotEmpty(message = "This field is required") 27 | private String lastName; 28 | 29 | private String alias; 30 | 31 | public User() {} 32 | 33 | public User(User user) { 34 | this(user.getId(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); 35 | this.alias = user.getAlias(); 36 | } 37 | 38 | public User(Long id, String email, String password, String firstName, 39 | String lastName) { 40 | this.id = id; 41 | this.email = email; 42 | this.password = password; 43 | this.firstName = firstName; 44 | this.lastName = lastName; 45 | } 46 | 47 | public Long getId() { 48 | return this.id; 49 | } 50 | 51 | public void setId(Long id) { 52 | this.id = id; 53 | } 54 | 55 | public String getEmail() { 56 | return this.email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getPassword() { 64 | return this.password; 65 | } 66 | 67 | public void setPassword(String password) { 68 | this.password = password; 69 | } 70 | 71 | public String getFirstName() { 72 | return this.firstName; 73 | } 74 | 75 | public void setFirstName(String firstName) { 76 | this.firstName = firstName; 77 | } 78 | 79 | public String getLastName() { 80 | return this.lastName; 81 | } 82 | 83 | public void setLastName(String lastName) { 84 | this.lastName = lastName; 85 | } 86 | 87 | public String getAlias() { 88 | return this.alias; 89 | } 90 | 91 | public void setAlias(String alias) { 92 | this.alias = alias; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/java/sample/tenants/user/TenantController.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Optional; 5 | import javax.validation.Valid; 6 | 7 | import org.springframework.http.MediaType; 8 | import org.springframework.web.bind.annotation.DeleteMapping; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | /** 19 | * @author Josh Cummings 20 | */ 21 | @RestController 22 | @RequestMapping(path="/tenants", produces = MediaType.APPLICATION_JSON_VALUE) 23 | public class TenantController { 24 | private static final SecureRandom random = new SecureRandom(); 25 | 26 | private final TenantRepository tenants; 27 | 28 | public TenantController(TenantRepository users) { 29 | this.tenants = users; 30 | } 31 | 32 | @GetMapping 33 | Iterable tenants() { 34 | return this.tenants.findAll(); 35 | } 36 | 37 | @GetMapping("/{id}") 38 | Optional findById(@PathVariable Long id) { 39 | return this.tenants.findById(id); 40 | } 41 | 42 | @GetMapping(params = "alias") 43 | Tenant findByAlias(@RequestParam String alias) { 44 | return this.tenants.findByAlias(alias); 45 | } 46 | 47 | @PostMapping 48 | Tenant add(@Valid @RequestBody Tenant tenant) { 49 | if (tenant.getId() == null) { 50 | tenant.setId(this.random.nextLong()); 51 | } 52 | return this.tenants.save(tenant); 53 | } 54 | 55 | @PutMapping 56 | Tenant update(@Valid @RequestBody Tenant tenant) { 57 | if (tenant.getId() == null) { 58 | throw new IllegalArgumentException("tenant does not exist, must POST first"); 59 | } 60 | return this.tenants.save(tenant); 61 | } 62 | 63 | @DeleteMapping("/{id}") 64 | Tenant delete(@PathVariable Long id) { 65 | return this.delete(id); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/schema/TenantHolderMultiTenantConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.schema; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | import javax.sql.DataSource; 6 | 7 | import org.hibernate.HibernateException; 8 | import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; 9 | import sample.multitenancy.Tenant; 10 | 11 | public class TenantHolderMultiTenantConnectionProvider implements MultiTenantConnectionProvider { 12 | 13 | private final DataSource dataSource; 14 | 15 | public TenantHolderMultiTenantConnectionProvider(DataSource dataSource) { 16 | this.dataSource = dataSource; 17 | } 18 | 19 | @Override 20 | public Connection getAnyConnection() throws SQLException { 21 | return dataSource.getConnection(); 22 | } 23 | 24 | @Override 25 | public void releaseAnyConnection(Connection connection) throws SQLException { 26 | connection.close(); 27 | } 28 | 29 | @Override 30 | public Connection getConnection(String tenantIdentifier) throws SQLException { 31 | final Connection connection = getAnyConnection(); 32 | try { 33 | connection.createStatement().execute( "USE " + tenantIdentifier ); 34 | } 35 | catch ( SQLException e ) { 36 | throw new HibernateException( 37 | "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", 38 | e 39 | ); 40 | } 41 | return connection; 42 | } 43 | 44 | @Override 45 | public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { 46 | try { 47 | connection.createStatement().execute( "USE " + Tenant.DEFAULT_TENANT ); 48 | } 49 | catch ( SQLException e ) { 50 | throw new HibernateException( 51 | "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", 52 | e 53 | ); 54 | } 55 | connection.close(); 56 | } 57 | 58 | @SuppressWarnings("rawtypes") 59 | @Override 60 | public boolean isUnwrappableAs(Class unwrapType) { 61 | return false; 62 | } 63 | 64 | @Override 65 | public T unwrap(Class unwrapType) { 66 | return null; 67 | } 68 | 69 | @Override 70 | public boolean supportsAggressiveRelease() { 71 | return true; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/src/main/java/sample/issuers/webflux/user/User.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.user; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.index.Indexed; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | /** 10 | * @author Rob Winch 11 | */ 12 | @Document 13 | public class User { 14 | @Id 15 | private Long id; 16 | 17 | @Indexed 18 | @NotEmpty(message = "This field is required") 19 | private String email; 20 | 21 | private String password; 22 | 23 | @NotEmpty(message = "This field is required") 24 | private String firstName; 25 | 26 | @NotEmpty(message = "This field is required") 27 | private String lastName; 28 | 29 | private String alias; 30 | 31 | public User() {} 32 | 33 | public User(User user) { 34 | this(user.getId(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); 35 | this.alias = user.getAlias(); 36 | } 37 | 38 | public User(Long id, String email, String password, String firstName, 39 | String lastName) { 40 | this.id = id; 41 | this.email = email; 42 | this.password = password; 43 | this.firstName = firstName; 44 | this.lastName = lastName; 45 | } 46 | 47 | public Long getId() { 48 | return this.id; 49 | } 50 | 51 | public void setId(Long id) { 52 | this.id = id; 53 | } 54 | 55 | public String getEmail() { 56 | return this.email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getPassword() { 64 | return this.password; 65 | } 66 | 67 | public void setPassword(String password) { 68 | this.password = password; 69 | } 70 | 71 | public String getFirstName() { 72 | return this.firstName; 73 | } 74 | 75 | public void setFirstName(String firstName) { 76 | this.firstName = firstName; 77 | } 78 | 79 | public String getLastName() { 80 | return this.lastName; 81 | } 82 | 83 | public void setLastName(String lastName) { 84 | this.lastName = lastName; 85 | } 86 | 87 | public String getAlias() { 88 | return this.alias; 89 | } 90 | 91 | public void setAlias(String alias) { 92 | this.alias = alias; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.security; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import reactor.core.publisher.Mono; 7 | 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 10 | import org.springframework.security.config.web.server.ServerHttpSecurity; 11 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 12 | import org.springframework.security.web.server.SecurityWebFilterChain; 13 | import org.springframework.security.web.server.ServerAuthenticationEntryPoint; 14 | import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; 15 | 16 | import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; 17 | 18 | @EnableWebFluxSecurity 19 | public class SecurityConfig { 20 | @Bean 21 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, 22 | ReactiveClientRegistrationRepository repository) { 23 | Map entryPoints = new HashMap<>(); 24 | // @formatter:off 25 | http 26 | .authorizeExchange() 27 | .pathMatchers("/login", "/webjars/**").permitAll() 28 | .anyExchange().authenticated() 29 | .and() 30 | .oauth2Login() 31 | .and() 32 | .oauth2Client() 33 | .and() 34 | .exceptionHandling() 35 | .authenticationEntryPoint((exchange, exception) -> { 36 | String host = exchange.getRequest().getHeaders().getFirst("Host"); 37 | String tenant = host.split("\\.")[0]; 38 | return repository.findByRegistrationId(tenant) 39 | .map(clientRegistration -> entryPoints.computeIfAbsent( 40 | clientRegistration.getRegistrationId(), 41 | registrationId -> new RedirectServerAuthenticationEntryPoint( 42 | DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + registrationId))) 43 | .switchIfEmpty(Mono.just(new RedirectServerAuthenticationEntryPoint("/login"))) 44 | .flatMap(entryPoint -> entryPoint.commence(exchange, exception)); 45 | }); 46 | 47 | return http.build(); 48 | // @formatter:on 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.security; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; 13 | import org.springframework.security.web.AuthenticationEntryPoint; 14 | import org.springframework.security.web.TenantAuthenticationEntryPoint; 15 | import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 16 | 17 | import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; 18 | import static org.springframework.security.web.TenantAuthenticationEntryPoint.resolveFromSubdomain; 19 | 20 | @EnableWebSecurity 21 | @Configuration 22 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 23 | 24 | @Autowired 25 | ClientRegistrationRepository repository; 26 | 27 | @Override 28 | protected void configure(HttpSecurity http) throws Exception { 29 | Map entryPoints = new HashMap<>(); 30 | AuthenticationEntryPoint authenticationEntryPoint = resolveFromSubdomain( 31 | tenant -> Optional.ofNullable(repository.findByRegistrationId(tenant)) 32 | .map(clientRegistration -> entryPoints.computeIfAbsent( 33 | clientRegistration.getRegistrationId(), 34 | registrationId -> new LoginUrlAuthenticationEntryPoint( 35 | DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + registrationId))) 36 | .orElse(new LoginUrlAuthenticationEntryPoint("/login"))); 37 | 38 | // @formatter:off 39 | http 40 | .authorizeRequests() 41 | .antMatchers("/login", "/webjars/**").permitAll() 42 | .anyRequest().authenticated() 43 | .and() 44 | .oauth2Login() 45 | .and() 46 | .oauth2Client() 47 | .and() 48 | .exceptionHandling() 49 | .authenticationEntryPoint(authenticationEntryPoint); 50 | // @formatter:on 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/java/sample/issuers/inbox/security/UserServiceOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.inbox.security; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | import sample.issuers.inbox.user.User; 7 | import sample.issuers.inbox.user.UserService; 8 | 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 11 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; 12 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; 13 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 14 | import org.springframework.security.oauth2.core.oidc.OidcIdToken; 15 | import org.springframework.security.oauth2.core.oidc.OidcUserInfo; 16 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 17 | import org.springframework.stereotype.Component; 18 | 19 | /** 20 | * @author Rob Winch 21 | */ 22 | @Component 23 | public class UserServiceOAuth2UserService 24 | implements OAuth2UserService { 25 | 26 | private final OidcUserService delegate = new OidcUserService(); 27 | 28 | private final UserService users; 29 | 30 | public UserServiceOAuth2UserService(UserService users) { 31 | this.users = users; 32 | } 33 | 34 | @Override 35 | public OidcUser loadUser(OidcUserRequest oidcUserRequest) 36 | throws OAuth2AuthenticationException { 37 | return create(this.delegate.loadUser(oidcUserRequest)); 38 | } 39 | 40 | private OidcUser create(OidcUser oidcUser) { 41 | return this.users.findByEmail(oidcUser.getEmail()) 42 | .map(u -> new CustomOidcUser(u, oidcUser)) 43 | .block(); 44 | } 45 | 46 | private class CustomOidcUser extends User implements OidcUser { 47 | private OidcUser oidcUser; 48 | 49 | public CustomOidcUser(User u, OidcUser oidcUser) { 50 | super(u); 51 | this.oidcUser = oidcUser; 52 | } 53 | 54 | public Map getClaims() { 55 | return oidcUser.getClaims(); 56 | } 57 | 58 | public OidcUserInfo getUserInfo() { 59 | return oidcUser.getUserInfo(); 60 | } 61 | 62 | public OidcIdToken getIdToken() { 63 | return oidcUser.getIdToken(); 64 | } 65 | 66 | public Collection getAuthorities() { 67 | return oidcUser.getAuthorities(); 68 | } 69 | 70 | public Map getAttributes() { 71 | return oidcUser.getAttributes(); 72 | } 73 | 74 | public String getName() { 75 | return oidcUser.getName(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/security/UserServiceOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.security; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | import sample.tenants.inbox.user.User; 7 | import sample.tenants.inbox.user.UserService; 8 | 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 11 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; 12 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; 13 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 14 | import org.springframework.security.oauth2.core.oidc.OidcIdToken; 15 | import org.springframework.security.oauth2.core.oidc.OidcUserInfo; 16 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 17 | import org.springframework.stereotype.Component; 18 | 19 | /** 20 | * @author Rob Winch 21 | */ 22 | @Component 23 | public class UserServiceOAuth2UserService 24 | implements OAuth2UserService { 25 | 26 | private final OidcUserService delegate = new OidcUserService(); 27 | 28 | private final UserService users; 29 | 30 | public UserServiceOAuth2UserService(UserService users) { 31 | this.users = users; 32 | } 33 | 34 | @Override 35 | public OidcUser loadUser(OidcUserRequest oidcUserRequest) 36 | throws OAuth2AuthenticationException { 37 | return create(this.delegate.loadUser(oidcUserRequest)); 38 | } 39 | 40 | private OidcUser create(OidcUser oidcUser) { 41 | return this.users.findByEmail(oidcUser.getEmail()) 42 | .map(u -> new CustomOidcUser(u, oidcUser)) 43 | .block(); 44 | } 45 | 46 | private class CustomOidcUser extends User implements OidcUser { 47 | private OidcUser oidcUser; 48 | 49 | public CustomOidcUser(User u, OidcUser oidcUser) { 50 | super(u); 51 | this.oidcUser = oidcUser; 52 | } 53 | 54 | public Map getClaims() { 55 | return oidcUser.getClaims(); 56 | } 57 | 58 | public OidcUserInfo getUserInfo() { 59 | return oidcUser.getUserInfo(); 60 | } 61 | 62 | public OidcIdToken getIdToken() { 63 | return oidcUser.getIdToken(); 64 | } 65 | 66 | public Collection getAuthorities() { 67 | return oidcUser.getAuthorities(); 68 | } 69 | 70 | public Map getAttributes() { 71 | return oidcUser.getAttributes(); 72 | } 73 | 74 | public String getName() { 75 | return oidcUser.getName(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /multiple-issuers/message/src/main/java/sample/issuers/message/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.message; 2 | 3 | import java.net.URL; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import com.nimbusds.jose.proc.JWSKeySelector; 8 | import com.nimbusds.jose.proc.SecurityContext; 9 | import com.nimbusds.jwt.proc.DefaultJWTProcessor; 10 | import com.nimbusds.jwt.proc.JWTClaimsSetAwareJWSKeySelector; 11 | 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.oauth2.jwt.JwtDecoder; 19 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 20 | 21 | import static com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL; 22 | 23 | @Configuration 24 | @EnableWebSecurity 25 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 26 | 27 | @Override 28 | protected void configure(HttpSecurity http) throws Exception { 29 | // @formatter:off 30 | http 31 | .authorizeRequests() 32 | .anyRequest().authenticated() 33 | .and() 34 | .oauth2ResourceServer() 35 | .jwt(); 36 | // @formatter:on 37 | } 38 | 39 | @Autowired 40 | IssuerRepository issuers; 41 | 42 | @Bean 43 | JwtDecoder jwtDecoder() { 44 | Map> keySelectors = new HashMap<>(); 45 | DefaultJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); 46 | JWTClaimsSetAwareJWSKeySelector keySelector = (header, claims, context) -> 47 | keySelectors.computeIfAbsent(claims.getIssuer(), this::fromIssuerUri) 48 | .selectJWSKeys(header, context); 49 | jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector); 50 | return new NimbusJwtDecoder(jwtProcessor); 51 | } 52 | 53 | JWSKeySelector fromIssuerUri(String uri) { 54 | return this.issuers.findByUri(uri) 55 | .map(this::fromIssuer) 56 | .orElseThrow(() -> new IllegalArgumentException("Unknown issuer " + uri)); 57 | } 58 | 59 | JWSKeySelector fromIssuer(Issuer issuer) { 60 | try { 61 | return fromJWKSetURL(new URL(issuer.getJwkSetUri())); 62 | } catch (Exception e) { 63 | throw new IllegalArgumentException(e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/MessageApplication.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import javax.sql.DataSource; 6 | 7 | import org.hibernate.MultiTenancyStrategy; 8 | import org.hibernate.cfg.Environment; 9 | import org.hibernate.context.spi.CurrentTenantIdentifierResolver; 10 | import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; 11 | import sample.multitenancy.TenantRepository; 12 | import sample.multitenancy.schema.TenantHolderMultiTenantConnectionProvider; 13 | import sample.multitenancy.schema.TenantCurrentTenantIdentifierResolver; 14 | import sample.multitenancy.web.WebClientTenantRepository; 15 | 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.boot.SpringApplication; 18 | import org.springframework.boot.autoconfigure.SpringBootApplication; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 21 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 22 | 23 | @SpringBootApplication 24 | public class MessageApplication { 25 | 26 | @Value("${tenants-url}") String tenantsUrl; 27 | 28 | @Bean 29 | TenantRepository tenantRepository() { 30 | return new WebClientTenantRepository(this.tenantsUrl); 31 | } 32 | 33 | @Bean 34 | LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { 35 | LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); 36 | MultiTenantConnectionProvider connectionProvider = new TenantHolderMultiTenantConnectionProvider(dataSource); 37 | CurrentTenantIdentifierResolver tenantIdentifierResolver = new TenantCurrentTenantIdentifierResolver(); 38 | 39 | HibernateJpaVendorAdapter hibernate = new HibernateJpaVendorAdapter(); 40 | 41 | factory.setDataSource(dataSource); 42 | factory.setJpaVendorAdapter(hibernate); 43 | factory.setPackagesToScan("sample.tenants.message"); 44 | 45 | Map properties = new HashMap<>(); 46 | properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA); 47 | properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider); 48 | properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver); 49 | properties.put(Environment.HBM2DDL_AUTO, "create"); 50 | properties.put(Environment.DIALECT, "org.hibernate.dialect.H2Dialect"); 51 | factory.setJpaPropertyMap(properties); 52 | return factory; 53 | } 54 | 55 | public static void main(String[] args) { 56 | SpringApplication.run(MessageApplication.class, args); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /multiple-issuers/inbox/src/main/resources/templates/users/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create User 6 | 7 | 8 | 9 |
10 |

Sign Up

11 |
12 |
13 | 14 | 15 | 18 |
19 |
20 | 21 | 22 | 25 |
26 |
27 | 28 | 29 | 32 |
33 |
34 | 35 | 36 | 39 |
40 | 41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/resources/templates/users/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create User 6 | 7 | 8 | 9 |
10 |

Sign Up

11 |
12 |
13 | 14 | 15 | 18 |
19 |
20 | 21 | 22 | 25 |
26 |
27 | 28 | 29 | 32 |
33 |
34 | 35 | 36 | 39 |
40 | 41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/resources/templates/users/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create User 6 | 7 | 8 | 9 |
10 |

Sign Up

11 |
12 |
13 | 14 | 15 | 18 |
19 |
20 | 21 | 22 | 25 |
26 |
27 | 28 | 29 | 32 |
33 |
34 | 35 | 36 | 39 |
40 | 41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/inbox/src/main/java/sample/issuers/webflux/inbox/security/ServiceReactiveOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package sample.issuers.webflux.inbox.security; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService; 5 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 6 | import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; 7 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 8 | import org.springframework.security.oauth2.core.oidc.OidcIdToken; 9 | import org.springframework.security.oauth2.core.oidc.OidcUserInfo; 10 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 11 | import org.springframework.stereotype.Component; 12 | import reactor.core.publisher.Mono; 13 | import sample.issuers.webflux.inbox.user.User; 14 | import sample.issuers.webflux.inbox.user.UserService; 15 | 16 | import java.util.Collection; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author Rob Winch 21 | */ 22 | @Component 23 | public class ServiceReactiveOAuth2UserService 24 | implements ReactiveOAuth2UserService { 25 | 26 | private final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService(); 27 | 28 | private final UserService users; 29 | 30 | public ServiceReactiveOAuth2UserService(UserService users) { 31 | this.users = users; 32 | } 33 | 34 | @Override 35 | public Mono loadUser(OidcUserRequest oidcUserRequest) 36 | throws OAuth2AuthenticationException { 37 | return this.delegate.loadUser(oidcUserRequest).flatMap(this::create); 38 | } 39 | 40 | private Mono create(OidcUser oidcUser) { 41 | return this.users.findByEmail(oidcUser.getEmail()) 42 | .map(u -> new CustomOidcUser(u, oidcUser)); 43 | } 44 | 45 | private class CustomOidcUser extends User implements OidcUser { 46 | private OidcUser oidcUser; 47 | 48 | public CustomOidcUser(User u, OidcUser oidcUser) { 49 | super(u); 50 | this.oidcUser = oidcUser; 51 | } 52 | 53 | public Map getClaims() { 54 | return oidcUser.getClaims(); 55 | } 56 | 57 | public OidcUserInfo getUserInfo() { 58 | return oidcUser.getUserInfo(); 59 | } 60 | 61 | public OidcIdToken getIdToken() { 62 | return oidcUser.getIdToken(); 63 | } 64 | 65 | public Collection getAuthorities() { 66 | return oidcUser.getAuthorities(); 67 | } 68 | 69 | public Map getAttributes() { 70 | return oidcUser.getAttributes(); 71 | } 72 | 73 | public String getName() { 74 | return oidcUser.getName(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tenant-domain-object/inbox/src/main/java/sample/tenants/inbox/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.inbox.security; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | import sample.multitenancy.Tenant; 8 | import sample.multitenancy.TenantHolder; 9 | import sample.multitenancy.TenantRepository; 10 | import sample.multitenancy.web.SubdomainTenantIdentifierResolver; 11 | import sample.multitenancy.web.TenantResolverFilter; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.web.AuthenticationEntryPoint; 19 | import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 20 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 21 | 22 | import static org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; 23 | 24 | @EnableWebSecurity 25 | @Configuration(proxyBeanMethods = false) 26 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 27 | 28 | @Autowired 29 | TenantRepository tenantRepository; 30 | 31 | @Override 32 | protected void configure(HttpSecurity http) throws Exception { 33 | TenantResolverFilter filter = new TenantResolverFilter(this.tenantRepository); 34 | filter.setTenantIdentifierConverter(new SubdomainTenantIdentifierResolver()); 35 | 36 | Map entryPoints = new HashMap<>(); 37 | AuthenticationEntryPoint authenticationEntryPoint = (request, response, e) -> 38 | Optional.ofNullable(TenantHolder.getTenant()) 39 | .map(tenant -> entryPoints.computeIfAbsent(tenant, this::fromTenant)) 40 | .orElse(new LoginUrlAuthenticationEntryPoint("/login")); 41 | 42 | // @formatter:off 43 | http 44 | .authorizeRequests() 45 | .antMatchers("/login", "/webjars/**").permitAll() 46 | .anyRequest().authenticated() 47 | .and() 48 | .oauth2Login() 49 | .and() 50 | .oauth2Client() 51 | .and() 52 | .exceptionHandling() 53 | .authenticationEntryPoint(authenticationEntryPoint) 54 | .and() 55 | .addFilterBefore(filter, BasicAuthenticationFilter.class); 56 | // @formatter:on 57 | } 58 | 59 | private AuthenticationEntryPoint fromTenant(Tenant tenant) { 60 | return new LoginUrlAuthenticationEntryPoint(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/" + tenant.getName()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/src/main/java/sample/tenants/user/Tenant.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | import java.util.Objects; 4 | import javax.validation.constraints.NotEmpty; 5 | 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | /** 10 | * @author Josh Cummings 11 | */ 12 | @Document 13 | public class Tenant { 14 | @Id 15 | private Long id; 16 | 17 | @NotEmpty(message = "This field is required") 18 | private String name; 19 | 20 | @NotEmpty(message = "This field is required") 21 | private String alias; 22 | 23 | @NotEmpty 24 | private String password; 25 | 26 | @NotEmpty(message = "This field is required") 27 | private String issuerUri; 28 | 29 | @NotEmpty(message = "This field is required") 30 | private String tokenVerificationMode; 31 | 32 | public Tenant() {} 33 | 34 | public Tenant(Tenant tenant) { 35 | this(tenant.getId(), tenant.getName(), tenant.getAlias(), 36 | tenant.getPassword(), tenant.getIssuerUri(), tenant.getTokenVerificationMode()); 37 | } 38 | 39 | public Tenant(Long id, String name, String alias, String password, String issuerUri, 40 | String tokenVerificationMode) { 41 | 42 | this.id = id; 43 | this.name = name; 44 | this.alias = alias; 45 | this.password = password; 46 | this.issuerUri = issuerUri; 47 | this.tokenVerificationMode = tokenVerificationMode; 48 | } 49 | 50 | public Long getId() { 51 | return this.id; 52 | } 53 | 54 | public void setId(Long id) { 55 | this.id = id; 56 | } 57 | 58 | public String getName() { 59 | return this.name; 60 | } 61 | 62 | public void setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | public String getAlias() { 67 | return this.alias; 68 | } 69 | 70 | public void setAlias(String alias) { 71 | this.alias = alias; 72 | } 73 | 74 | public String getPassword() { 75 | return password; 76 | } 77 | 78 | public void setPassword(String password) { 79 | this.password = password; 80 | } 81 | 82 | public String getIssuerUri() { 83 | return issuerUri; 84 | } 85 | 86 | public void setIssuerUri(String issuerUri) { 87 | this.issuerUri = issuerUri; 88 | } 89 | 90 | public String getTokenVerificationMode() { 91 | return tokenVerificationMode; 92 | } 93 | 94 | public void setTokenVerificationMode(String tokenVerificationMode) { 95 | this.tokenVerificationMode = tokenVerificationMode; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return this.alias; 101 | } 102 | 103 | @Override 104 | public boolean equals(Object o) { 105 | if (this == o) return true; 106 | if (o == null || getClass() != o.getClass()) return false; 107 | Tenant tenant = (Tenant) o; 108 | return this.alias.equals(tenant.alias); 109 | } 110 | 111 | @Override 112 | public int hashCode() { 113 | return Objects.hash(alias); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /multi-tenancy/src/main/java/sample/multitenancy/schema/MultiTenantSchemaManagementTool.java: -------------------------------------------------------------------------------- 1 | package sample.multitenancy.schema; 2 | 3 | import java.sql.SQLException; 4 | import java.sql.Statement; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; 9 | import org.hibernate.service.spi.ServiceRegistryAwareService; 10 | import org.hibernate.service.spi.ServiceRegistryImplementor; 11 | import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool; 12 | import org.hibernate.tool.schema.internal.SchemaCreatorImpl; 13 | import org.hibernate.tool.schema.internal.SchemaDropperImpl; 14 | import org.hibernate.tool.schema.internal.exec.JdbcContext; 15 | import org.hibernate.tool.schema.spi.SchemaCreator; 16 | import org.hibernate.tool.schema.spi.SchemaDropper; 17 | import org.hibernate.tool.schema.spi.SchemaManagementTool; 18 | import org.hibernate.tool.schema.spi.SchemaMigrator; 19 | import org.hibernate.tool.schema.spi.SchemaValidator; 20 | 21 | public class MultiTenantSchemaManagementTool implements SchemaManagementTool, ServiceRegistryAwareService { 22 | List tenants; 23 | HibernateSchemaManagementTool tool = new HibernateSchemaManagementTool(); 24 | 25 | public MultiTenantSchemaManagementTool(List tenants) { 26 | this.tenants = tenants; 27 | } 28 | 29 | @Override 30 | public SchemaCreator getSchemaCreator(Map options) { 31 | return (metadata, options1, sourceDescriptor, targetDescriptor) -> { 32 | JdbcContext context = tool.resolveJdbcContext(options); 33 | DdlTransactionIsolator isolator = tool.getDdlTransactionIsolator(context); 34 | SchemaCreator creator = new SchemaCreatorImpl(this.tool); 35 | 36 | for (String tenant : tenants) { 37 | createSchemaIfNecessary(isolator, tenant); 38 | prepareTenant(isolator, tenant); 39 | creator.doCreation(metadata, options1, sourceDescriptor, targetDescriptor); 40 | } 41 | 42 | isolator.release(); 43 | }; 44 | } 45 | 46 | @Override 47 | public SchemaDropper getSchemaDropper(Map options) { 48 | return new SchemaDropperImpl(this.tool); 49 | } 50 | 51 | @Override 52 | public SchemaMigrator getSchemaMigrator(Map options) { 53 | throw new UnsupportedOperationException("unsupported"); 54 | } 55 | 56 | @Override 57 | public SchemaValidator getSchemaValidator(Map options) { 58 | throw new UnsupportedOperationException("unsupported"); 59 | } 60 | 61 | private void createSchemaIfNecessary(DdlTransactionIsolator isolator, String tenant) { 62 | try { 63 | Statement stmt = isolator.getIsolatedConnection().createStatement(); 64 | stmt.execute("CREATE SCHEMA IF NOT EXISTS " + tenant); 65 | } catch (SQLException e) { 66 | throw new IllegalStateException(e); 67 | } 68 | } 69 | 70 | private void prepareTenant(DdlTransactionIsolator isolator, String tenant) { 71 | try { 72 | Statement stmt = isolator.getIsolatedConnection().createStatement(); 73 | stmt.execute("SET SCHEMA " + tenant); 74 | } catch (SQLException e) { 75 | throw new IllegalStateException(e); 76 | } 77 | } 78 | 79 | @Override 80 | public void injectServices(ServiceRegistryImplementor serviceRegistry) { 81 | this.tool.injectServices(serviceRegistry); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tenant-domain-object/user/src/main/java/sample/tenants/user/UserSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.user; 2 | 3 | 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | import sample.multitenancy.Tenant; 10 | import sample.multitenancy.TenantHolder; 11 | import sample.multitenancy.TenantRepository; 12 | import sample.multitenancy.web.TenantResolverFilter; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.security.authentication.AuthenticationManager; 17 | import org.springframework.security.authentication.AuthenticationManagerResolver; 18 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.security.oauth2.jwt.JwtDecoder; 21 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; 22 | import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; 23 | import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; 24 | import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; 25 | import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; 26 | 27 | import static org.springframework.security.oauth2.jwt.JwtDecoders.fromIssuerLocation; 28 | 29 | @Configuration 30 | public class UserSecurityConfig extends WebSecurityConfigurerAdapter { 31 | 32 | @Autowired 33 | TenantRepository tenantRepository; 34 | 35 | @Override 36 | protected void configure(HttpSecurity http) throws Exception { 37 | TenantResolverFilter filter = new TenantResolverFilter(this.tenantRepository); 38 | http 39 | .authorizeRequests() 40 | .anyRequest().authenticated() 41 | .and() 42 | .oauth2ResourceServer() 43 | .authenticationManagerResolver(authenticationManagerResolver()) 44 | .and() 45 | .addFilterBefore(filter, BearerTokenAuthenticationFilter.class); 46 | } 47 | 48 | 49 | private AuthenticationManagerResolver authenticationManagerResolver() { 50 | Map authenticationManagers = new HashMap<>(); 51 | return request -> 52 | Optional.ofNullable(TenantHolder.getTenant()) 53 | .map(tenant -> authenticationManagers.computeIfAbsent(tenant, this::fromTenant)) 54 | .orElseThrow(() -> new IllegalArgumentException("Unknown tenant")); 55 | } 56 | 57 | AuthenticationManager fromTenant(Tenant tenant) { 58 | String tokenVerificationMode = tenant.getAttribute("tokenVerificationMode"); 59 | if ("jwt".equals(tokenVerificationMode)) { 60 | JwtDecoder jwtDecoder = fromIssuerLocation(tenant.getAttribute("issuerUri")); 61 | return new JwtAuthenticationProvider(jwtDecoder)::authenticate; 62 | } 63 | if ("opaqueToken".equals(tokenVerificationMode)) { 64 | OpaqueTokenIntrospector client = 65 | new NimbusOpaqueTokenIntrospector( 66 | tenant.getAttribute("issuerUri") + "/introspect", 67 | tenant.getName(), 68 | (String) tenant.getCredentials()); 69 | return new OpaqueTokenAuthenticationProvider(client)::authenticate; 70 | } 71 | throw new IllegalArgumentException("Unsupported token verification mode " + tokenVerificationMode); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /multiple-issuers/user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.multiple-issuers 8 | user 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | user 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.2.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-mongodb 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | de.flapdoodle.embed 40 | de.flapdoodle.embed.mongo 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | 60 | 61 | spring-snapshots 62 | Spring Snapshots 63 | https://repo.spring.io/snapshot 64 | 65 | true 66 | 67 | 68 | 69 | spring-milestones 70 | Spring Milestones 71 | https://repo.spring.io/milestone 72 | 73 | false 74 | 75 | 76 | 77 | 78 | 79 | 80 | spring-snapshots 81 | Spring Snapshots 82 | https://repo.spring.io/snapshot 83 | 84 | true 85 | 86 | 87 | 88 | spring-milestones 89 | Spring Milestones 90 | https://repo.spring.io/milestone 91 | 92 | false 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /tenant-domain-object/tenant/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.tenants 8 | tenant 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | user 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.2.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-mongodb 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | de.flapdoodle.embed 40 | de.flapdoodle.embed.mongo 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | 60 | 61 | spring-snapshots 62 | Spring Snapshots 63 | https://repo.spring.io/snapshot 64 | 65 | true 66 | 67 | 68 | 69 | spring-milestones 70 | Spring Milestones 71 | https://repo.spring.io/milestone 72 | 73 | false 74 | 75 | 76 | 77 | 78 | 79 | 80 | spring-snapshots 81 | Spring Snapshots 82 | https://repo.spring.io/snapshot 83 | 84 | true 85 | 86 | 87 | 88 | spring-milestones 89 | Spring Milestones 90 | https://repo.spring.io/milestone 91 | 92 | false 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /tenant-domain-object/message/src/main/java/sample/tenants/message/MessageSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package sample.tenants.message; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import sample.multitenancy.Tenant; 9 | import sample.multitenancy.TenantHolder; 10 | import sample.multitenancy.TenantRepository; 11 | import sample.multitenancy.web.TenantResolverFilter; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.security.authentication.AuthenticationManager; 16 | import org.springframework.security.authentication.AuthenticationManagerResolver; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.security.oauth2.jwt.JwtDecoder; 21 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; 22 | import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; 23 | import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; 24 | import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; 25 | import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; 26 | 27 | import static org.springframework.security.oauth2.jwt.JwtDecoders.fromIssuerLocation; 28 | 29 | @Configuration 30 | @EnableWebSecurity 31 | public class MessageSecurityConfig extends WebSecurityConfigurerAdapter { 32 | 33 | @Autowired 34 | TenantRepository tenantRepository; 35 | 36 | @Override 37 | protected void configure(HttpSecurity http) throws Exception { 38 | TenantResolverFilter filter = new TenantResolverFilter(this.tenantRepository); 39 | 40 | // @formatter:off 41 | http 42 | .authorizeRequests() 43 | .anyRequest().authenticated() 44 | .and() 45 | .oauth2ResourceServer() 46 | .authenticationManagerResolver(authenticationManagerResolver()) 47 | .and() 48 | .addFilterBefore(filter, BearerTokenAuthenticationFilter.class); 49 | // @formatter:on 50 | } 51 | 52 | AuthenticationManagerResolver authenticationManagerResolver() { 53 | Map authenticationManagers = new HashMap<>(); 54 | return request -> Optional.ofNullable(TenantHolder.getTenant()) 55 | .map(tenant -> authenticationManagers.computeIfAbsent(tenant, this::fromTenant)) 56 | .orElseThrow(() -> new IllegalArgumentException("Unknown tenant")); 57 | } 58 | 59 | AuthenticationManager fromTenant(Tenant tenant) { 60 | String tokenVerificationMode = tenant.getAttribute("tokenVerificationMode"); 61 | if ("jwt".equals(tokenVerificationMode)) { 62 | JwtDecoder jwtDecoder = fromIssuerLocation(tenant.getAttribute("issuerUri")); 63 | return new JwtAuthenticationProvider(jwtDecoder)::authenticate; 64 | } 65 | if ("opaqueToken".equals(tokenVerificationMode)) { 66 | OpaqueTokenIntrospector client = 67 | new NimbusOpaqueTokenIntrospector( 68 | tenant.getAttribute("issuerUri") + "/introspect", 69 | tenant.getName(), 70 | (String) tenant.getCredentials()); 71 | return new OpaqueTokenAuthenticationProvider(client)::authenticate; 72 | } 73 | throw new IllegalArgumentException("Unsupported token verification mode " + tokenVerificationMode); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /multiple-issuers-webflux/user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.multiple-issuers-webflux 8 | user 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | user 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.2.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-mongodb-reactive 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-webflux 36 | 37 | 38 | de.flapdoodle.embed 39 | de.flapdoodle.embed.mongo 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-test 45 | test 46 | 47 | 48 | io.projectreactor 49 | reactor-test 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-maven-plugin 59 | 60 | 61 | 62 | 63 | 64 | 65 | spring-snapshots 66 | Spring Snapshots 67 | https://repo.spring.io/snapshot 68 | 69 | true 70 | 71 | 72 | 73 | spring-milestones 74 | Spring Milestones 75 | https://repo.spring.io/milestone 76 | 77 | false 78 | 79 | 80 | 81 | 82 | 83 | 84 | spring-snapshots 85 | Spring Snapshots 86 | https://repo.spring.io/snapshot 87 | 88 | true 89 | 90 | 91 | 92 | spring-milestones 93 | Spring Milestones 94 | https://repo.spring.io/milestone 95 | 96 | false 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /tenant-domain-object/user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.tenants 8 | user 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | user 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.2.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | sample 31 | multi-tenancy 32 | 0.0.1-SNAPSHOT 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-jpa 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-oauth2-resource-server 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | 46 | 47 | 48 | com.h2database 49 | h2 50 | 51 | 52 | org.projectlombok 53 | lombok 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-test 59 | test 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 69 | 70 | 71 | 72 | 73 | 74 | spring-snapshots 75 | Spring Snapshots 76 | https://repo.spring.io/snapshot 77 | 78 | true 79 | 80 | 81 | 82 | spring-milestones 83 | Spring Milestones 84 | https://repo.spring.io/milestone 85 | 86 | false 87 | 88 | 89 | 90 | 91 | 92 | 93 | spring-snapshots 94 | Spring Snapshots 95 | https://repo.spring.io/snapshot 96 | 97 | true 98 | 99 | 100 | 101 | spring-milestones 102 | Spring Milestones 103 | https://repo.spring.io/milestone 104 | 105 | false 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /multiple-issuers/message/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sample.multiple-issuers 8 | message 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | message 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.2.0.M6 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-mongodb 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-oauth2-resource-server 36 | 2.2.0.BUILD-SNAPSHOT 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | 44 | com.nimbusds 45 | nimbus-jose-jwt 46 | 7.5.1 47 | 48 | 49 | com.nimbusds 50 | oauth2-oidc-sdk 51 | 6.13 52 | 53 | 54 | de.flapdoodle.embed 55 | de.flapdoodle.embed.mongo 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | spring-snapshots 78 | Spring Snapshots 79 | https://repo.spring.io/snapshot 80 | 81 | true 82 | 83 | 84 | 85 | spring-milestones 86 | Spring Milestones 87 | https://repo.spring.io/milestone 88 | 89 | false 90 | 91 | 92 | 93 | 94 | 95 | 96 | spring-snapshots 97 | Spring Snapshots 98 | https://repo.spring.io/snapshot 99 | 100 | true 101 | 102 | 103 | 104 | spring-milestones 105 | Spring Milestones 106 | https://repo.spring.io/milestone 107 | 108 | false 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /tenant-domain-object/gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | sample.tenants 7 | gateway-sample 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | gateway-sample 12 | Demo project for Spring Cloud Gateway 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.2.0.M6 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Greenwich.RELEASE 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-security 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-webflux 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-starter 44 | 45 | 46 | org.springframework.cloud 47 | spring-cloud-starter-gateway 48 | 49 | 50 | org.springframework.security 51 | spring-security-oauth2-jose 52 | 53 | 54 | org.springframework.security 55 | spring-security-oauth2-resource-server 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | io.projectreactor 65 | reactor-test 66 | test 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.cloud 74 | spring-cloud-dependencies 75 | ${spring-cloud.version} 76 | pom 77 | import 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-maven-plugin 87 | 88 | 89 | 90 | 91 | 92 | 93 | spring-snapshots 94 | Spring Snapshots 95 | https://repo.spring.io/snapshot 96 | 97 | true 98 | 99 | 100 | 101 | spring-milestones 102 | Spring Milestones 103 | https://repo.spring.io/milestone 104 | 105 | false 106 | 107 | 108 | 109 | 110 | 111 | 112 | spring-snapshots 113 | Spring Snapshots 114 | https://repo.spring.io/snapshot 115 | 116 | true 117 | 118 | 119 | 120 | spring-milestones 121 | Spring Milestones 122 | https://repo.spring.io/milestone 123 | 124 | false 125 | 126 | 127 | 128 | 129 | 130 | 131 | --------------------------------------------------------------------------------