diff --git a/README.md b/README.md
index 96c2623..e87925b 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,21 @@
# Java Microservices with Spring Boot and Spring Cloud 🍃☁️
-This repository contains examples of how to build a microservices architecture with Spring Boot 2.1, Spring Cloud Greenwich, Netflix Eureka, and Netflix Zuul.
+This repository contains examples of how to build a Java microservices architecture with Spring Boot 2.1, Spring Cloud Greenwich, and Netflix Eureka.
-This repository has two examples in it. One is a bare-bones microservices architecture with Spring Boot, Spring Cloud, Eureka Server, and Zuul. The second is one that's built with JHipster and configured centrally with Spring Cloud Config.
+This repository has three examples in it. The first is a bare-bones microservices architecture with Spring Boot, Spring Cloud, Eureka Server, and Zuul. The second is one that's built with JHipster and configured centrally with Spring Cloud Config. The third uses Spring Cloud Gateway and Spring WebFlux to show reactive microservices.
We think you'll enjoy them both!
* See [Java Microservices with Spring Boot and Spring Cloud](https://developer.okta.com/blog/2019/05/22/java-microservices-spring-boot-spring-cloud) for an overview of the first example.
* Read [Java Microservices with Spring Cloud Config and JHipster](https://developer.okta.com/blog/2019/05/23/java-microservices-spring-cloud-config) to learn about microservices with JHipster.
+* Refer to [Secure Reactive Microservices with Spring Cloud Gateway](https://developer.okta.com/blog/2019/08/28/reactive-microservices-spring-cloud-gateway) to learn about Spring Cloud Gateway and reactive microservices.
-**Prerequisites:** [Java 11](https://sdkman.io/sdks#java).
+**Prerequisites:** [Java 11](https://sdkman.io/sdks#java) and an internet connection.
* [Spring Boot + Spring Cloud Example](#spring-boot--spring-cloud-example)
* [JHipster + Spring Cloud Config Example](#jhipster--spring-cloud-config-example)
+* [Spring Cloud Gateway Example](#spring-cloud-gateway-example)
* [Links](#links)
* [Help](#help)
* [License](#license)
@@ -47,7 +49,7 @@ okta.oauth2.client-secret=$clientSecret
Then, run all the projects with `./mvnw` in separate terminal windows. You should be able to navigate to `http://localhost:8761` and see the apps have been registered with Eureka.
-Then, navigate to `http://localhost:8080/cool-bars` in your browser, log in with Okta, and see the resulting JSON.
+Then, navigate to `http://localhost:8080/cool-cars` in your browser, log in with Okta, and see the resulting JSON.
## JHipster + Spring Cloud Config Example
@@ -119,6 +121,43 @@ Create a `ROLE_ADMIN` group (**Users** > **Groups** > **Add Group**) and add you
Now when you hit `http://localhost:8761` or `http://localhost:8080`, you should be able to log in with Okta!
+## Spring Cloud Gateway Example
+
+To install this example, run the following commands:
+
+```bash
+git clone https://github.com/oktadeveloper/java-microservices-examples.git
+cd java-microservices-examples/spring-cloud-gateway
+```
+
+The `api-gateway` and `car-service` projects are already pre-configured to be locked down with OAuth 2.0 and Okta. That means if you try to run them, you won't be able to login until you create an account, and an application in it.
+
+If you already have an Okta account, see the **Create a Web Application in Okta** section below. Otherwise, we created a Maven plugin that configures a free Okta developer account + an OIDC app (in under a minute!).
+
+To use it, run `./mvnw com.okta:okta-maven-plugin:setup` to create an account and configure the gateway to work with Okta.
+
+Copy the `okta.*` properties from the gateway's `src/main/resources/application.properties` to the same file in the `car-service` project.
+
+Then, run all the projects with `./mvnw` in separate terminal windows. You should be able to navigate to `http://localhost:8761` and see the apps have been registered with Eureka.
+
+Then, navigate to `http://localhost:8080/cars` in your browser, log in with Okta, and see the resulting JSON.
+
+### Create a Web Application in Okta
+
+Log in to your Okta Developer account (or [sign up](https://developer.okta.com/signup/) if you don't have an account).
+
+1. From the **Applications** page, choose **Add Application**.
+2. On the Create New Application page, select **Web**.
+3. Give your app a memorable name, add `http://localhost:8080/login/oauth2/code/okta` as a Login redirect URI and click **Done**.
+
+Copy the issuer (found under **API** > **Authorization Servers**), client ID, and client secret into the `application.properties` of the `api-gateway` and `car-service` projects.
+
+```properties
+okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
+okta.oauth2.client-id=$clientId
+okta.oauth2.client-secret=$clientSecret
+```
+
## Links
These examples uses the following open source libraries:
@@ -126,6 +165,7 @@ These examples uses the following open source libraries:
* [Okta Spring Boot Starter](https://github.com/okta/okta-spring-boot)
* [Spring Boot](https://spring.io/projects/spring-boot)
* [Spring Cloud](https://spring.io/projects/spring-cloud)
+* [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway)
* [Spring Security](https://spring.io/projects/spring-security)
* [JHipster](https://www.jhipster.tech)
* [OpenJDK](https://openjdk.java.net/)
diff --git a/jhipster/demo.adoc b/jhipster/demo.adoc
new file mode 100644
index 0000000..43f9c5c
--- /dev/null
+++ b/jhipster/demo.adoc
@@ -0,0 +1,177 @@
+:experimental:
+// Define unicode for Apple Command key.
+:commandkey: ⌘
+
+= Java Microservices with Spring Cloud Config and JHipster
+
+If you'd like to see a video version of this demo, you can https://www.youtube.com/watch?v=ez7HMO60kE8[watch it on YouTube].
+
+== Create Microservices with JHipster
+
+. Use https://start.jhipster.tech[JDL Studio] to create a microservices architecture using JDL (gateway, blog, and store)
+
+.`apps.jh`
+[%collapsible]
+====
+[source]
+----
+application {
+ config {
+ baseName gateway,
+ packageName com.okta.developer.gateway,
+ applicationType gateway,
+ authenticationType oauth2,
+ prodDatabaseType postgresql,
+ serviceDiscoveryType eureka,
+ testFrameworks [protractor]
+ }
+ entities Blog, Post, Tag, Product
+}
+
+application {
+ config {
+ baseName blog,
+ packageName com.okta.developer.blog,
+ applicationType microservice,
+ authenticationType oauth2,
+ prodDatabaseType postgresql,
+ serverPort 8081,
+ serviceDiscoveryType eureka
+ }
+ entities Blog, Post, Tag
+}
+
+application {
+ config {
+ baseName store,
+ packageName com.okta.developer.store,
+ applicationType microservice,
+ authenticationType oauth2,
+ databaseType mongodb,
+ devDatabaseType mongodb,
+ prodDatabaseType mongodb,
+ enableHibernateCache false,
+ serverPort 8082,
+ serviceDiscoveryType eureka
+ }
+ entities Product
+}
+
+entity Blog {
+ name String required minlength(3),
+ handle String required minlength(2)
+}
+
+entity Post {
+ title String required,
+ content TextBlob required,
+ date Instant required
+}
+
+entity Tag {
+ name String required minlength(2)
+}
+
+entity Product {
+ title String required,
+ price BigDecimal required min(0),
+ image ImageBlob
+}
+
+relationship ManyToOne {
+ Blog{user(login)} to User,
+ Post{blog(name)} to Blog
+}
+
+relationship ManyToMany {
+ Post{tag(name)} to Tag{post}
+}
+
+paginate Post, Tag with infinite-scroll
+paginate Product with pagination
+
+microservice Product with store
+microservice Blog, Post, Tag with blog
+
+// will be created under 'docker-compose' folder
+deployment {
+ deploymentType docker-compose
+ appsFolders [gateway, blog, store]
+ dockerRepositoryName "jmicro"
+ consoleOptions [zipkin]
+}
+----
+====
+[start=2]
+. Run `jhipster import-jdl apps.jh`
+
+. Create an aggregator `pom.xml` in the root directory
+
+
+
+ 4.0.0
+ com.okta.developer
+ jhipster-parent
+ 1.0.0-SNAPSHOT
+ pom
+ jhipster-parent
+
+ gateway
+ blog
+ store
+
+
+
+. Convert gateway to be a PWA
+
+. Create Docker images for Spring Boot apps
+
+ mvn -Pprod verify com.google.cloud.tools:jib-maven-plugin:dockerBuild
+
+. Run with Docker Compose
+
+ cd docker-compose
+ docker-compose up -d
+
+. Mention you need a `hosts` entry for Keycloak
+
+ 127.0.0.1 keycloak
+
+. Show JHipster Registry at `http://localhost:8761` and gateway at `http://localhost:8080`
+
+== Configure JHipster Microservices to use Okta for Identity
+
+. Create a web app on Okta, use `http://localhost:8080/login/oauth2/code/oidc` for redirect URI
+
+. Add `http://localhost:8080` as a logout redirect URI
+
+. Add your Okta settings in Spring Cloud Config's `docker-compose/central-server-config/application.yml`
+
+ spring:
+ security:
+ oauth2:
+ client:
+ provider:
+ oidc:
+ issuer-uri: https://{yourOktaDomain}/oauth2/default
+ registration:
+ oidc:
+ client-id: {yourClientId}
+ client-secret: {yourClientSecret}
+
+. Restart all containers using `docker-compose restart`
+
+. Prepare Okta for JHipster: `ROLE_ADMIN` group, groups in ID token, and JHipster Registry's redirect URIs
+
+. Demo JHipster Registry and gateway log in with Okta
+
+. Show Lighthouse Score
+
+. Finito! 🤓
+
+== Learn More!
+
+. GitHub repo: https://github.com/oktadeveloper/java-microservices-examples
+
+. Blog post: https://developer.okta.com/blog/2019/05/23/java-microservices-spring-cloud-config
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0.svg b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0.svg
index 1f9ab52..fd59561 100755
--- a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0.svg
+++ b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0.svg
@@ -1,198 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-192.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-192.png
index d64fd39..3c39b75 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-192.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-192.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-256.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-256.png
index 7e720c0..8e2e5b1 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-256.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-256.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-384.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-384.png
index 0cc25a5..823afbf 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-384.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-384.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-512.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-512.png
index e2a5a11..0b2bc6a 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-512.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_0_head-512.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1.svg b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1.svg
index 7a118f3..508aa1e 100755
--- a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1.svg
+++ b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1.svg
@@ -1,9387 +1 @@
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-192.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-192.png
index b702e6b..991a518 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-192.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-192.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-256.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-256.png
index 4c04212..f8ab21b 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-256.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-256.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-384.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-384.png
index 308e78f..a15b7de 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-384.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-384.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-512.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-512.png
index 4bd57d6..c97fa83 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-512.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_1_head-512.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2.svg b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2.svg
index 1747933..69f3f43 100755
--- a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2.svg
+++ b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2.svg
@@ -1,841 +1 @@
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-256.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-256.png
index c9af2a8..2e9ce66 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-256.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-256.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-384.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-384.png
index aefb050..5e84fd9 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-384.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-384.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-512.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-512.png
index 3d61240..d3b5677 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-512.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_2_head-512.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3.svg b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3.svg
index 6b9e056..fbbe8f7 100755
--- a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3.svg
+++ b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3.svg
@@ -1,308 +1 @@
-
-
-
+
\ No newline at end of file
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-192.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-192.png
index 3ee38de..0c476e5 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-192.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-192.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-256.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-256.png
index d671166..1c42812 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-256.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-256.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-384.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-384.png
index 2624c29..435c4b1 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-384.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-384.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-512.png b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-512.png
index 76db968..e45df5d 100644
Binary files a/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-512.png and b/jhipster/gateway/src/main/webapp/content/images/jhipster_family_member_3_head-512.png differ
diff --git a/jhipster/gateway/src/main/webapp/content/images/logo-jhipster.png b/jhipster/gateway/src/main/webapp/content/images/logo-jhipster.png
old mode 100755
new mode 100644
index 5d31c2f..804890e
Binary files a/jhipster/gateway/src/main/webapp/content/images/logo-jhipster.png and b/jhipster/gateway/src/main/webapp/content/images/logo-jhipster.png differ
diff --git a/jhipster/gateway/src/main/webapp/swagger-ui/dist/images/throbber.gif b/jhipster/gateway/src/main/webapp/swagger-ui/dist/images/throbber.gif
index 0639388..0a55cf0 100644
Binary files a/jhipster/gateway/src/main/webapp/swagger-ui/dist/images/throbber.gif and b/jhipster/gateway/src/main/webapp/swagger-ui/dist/images/throbber.gif differ
diff --git a/spring-boot+cloud/api-gateway/pom.xml b/spring-boot+cloud/api-gateway/pom.xml
index 371a56a..00e2c39 100755
--- a/spring-boot+cloud/api-gateway/pom.xml
+++ b/spring-boot+cloud/api-gateway/pom.xml
@@ -5,7 +5,7 @@
org.springframework.bootspring-boot-starter-parent
- 2.1.5.RELEASE
+ 2.2.5.RELEASEcom.example
@@ -16,7 +16,7 @@
1.8
- Greenwich.SR1
+ Hoxton.SR3
@@ -47,7 +47,7 @@
com.okta.springokta-spring-boot-starter
- 1.2.0
+ 1.4.0org.projectlombok
diff --git a/spring-boot+cloud/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java b/spring-boot+cloud/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
index 900914a..d5bb6ef 100755
--- a/spring-boot+cloud/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
+++ b/spring-boot+cloud/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
@@ -14,7 +14,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
-import org.springframework.hateoas.Resources;
+import org.springframework.hateoas.CollectionModel;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
@@ -92,7 +92,7 @@ interface CarClient {
@GetMapping("/cars")
@CrossOrigin
- Resources readCars();
+ CollectionModel readCars();
}
@RestController
diff --git a/spring-boot+cloud/api-gateway/src/main/resources/application.properties b/spring-boot+cloud/api-gateway/src/main/resources/application.properties
index 7256e35..45e869e 100755
--- a/spring-boot+cloud/api-gateway/src/main/resources/application.properties
+++ b/spring-boot+cloud/api-gateway/src/main/resources/application.properties
@@ -1,7 +1,7 @@
spring.application.name=api-gateway
-okta.oauth2.issuer=https://dev-737523.oktapreview.com/oauth2/default
-okta.oauth2.client-id=0oafx05pu2pxhjgkC0h7
-okta.oauth2.client-secret=ozxBeuk7nE-oLhkUvINe1cxR3LITquTp7Jt2NvX7
+okta.oauth2.issuer=https://dev-133320.okta.com/oauth2/default
+okta.oauth2.client-id=0oa35aqn81ftCrz53357
+okta.oauth2.client-secret=lchv-e8ZLN3si7v84tVq-W4OT1l7kzkE5irBvDhs
feign.hystrix.enabled=true
hystrix.shareSecurityContext=true
diff --git a/spring-boot+cloud/car-service/pom.xml b/spring-boot+cloud/car-service/pom.xml
index 1ba35c4..90d627f 100755
--- a/spring-boot+cloud/car-service/pom.xml
+++ b/spring-boot+cloud/car-service/pom.xml
@@ -5,7 +5,7 @@
org.springframework.bootspring-boot-starter-parent
- 2.1.5.RELEASE
+ 2.2.5.RELEASEcom.example
@@ -16,7 +16,7 @@
1.8
- Greenwich.SR1
+ Hoxton.SR3
@@ -43,7 +43,7 @@
com.okta.springokta-spring-boot-starter
- 1.2.0
+ 1.4.0org.springframework.boot
diff --git a/spring-boot+cloud/car-service/src/main/resources/application.properties b/spring-boot+cloud/car-service/src/main/resources/application.properties
index e7d4fa3..7f79ddb 100755
--- a/spring-boot+cloud/car-service/src/main/resources/application.properties
+++ b/spring-boot+cloud/car-service/src/main/resources/application.properties
@@ -1,5 +1,5 @@
server.port=8090
spring.application.name=car-service
-okta.oauth2.issuer=https://dev-737523.oktapreview.com/oauth2/default
-okta.oauth2.client-id=0oafx05pu2pxhjgkC0h7
-okta.oauth2.client-secret=ozxBeuk7nE-oLhkUvINe1cxR3LITquTp7Jt2NvX7
+okta.oauth2.issuer=https://dev-133320.okta.com/oauth2/default
+okta.oauth2.client-id=0oa35aqn81ftCrz53357
+okta.oauth2.client-secret=lchv-e8ZLN3si7v84tVq-W4OT1l7kzkE5irBvDhs
diff --git a/spring-boot+cloud/demo.adoc b/spring-boot+cloud/demo.adoc
new file mode 100644
index 0000000..10ba73a
--- /dev/null
+++ b/spring-boot+cloud/demo.adoc
@@ -0,0 +1,152 @@
+:experimental:
+// Define unicode for Apple Command key.
+:commandkey: ⌘
+
+= Java Microservices with Spring Boot and Spring Cloud
+
+The brackets at the end of each step indicate the alias's or IntelliJ Live Templates to use. You can find the template definitions at https://github.com/mraible/idea-live-templates[mraible/idea-live-templates].
+
+== Create Eureka Server, Car Service, and API Gateway
+
+. Create a Eureka Server
+
+ http https://start.spring.io/starter.zip javaVersion==11 \
+ artifactId==discovery-service name==eureka-service \
+ dependencies==cloud-eureka-server baseDir==discovery-service | tar -xzvf -
+
+. Create a Car Service
+
+ http https://start.spring.io/starter.zip \
+ artifactId==car-service name==car-service baseDir==car-service \
+ dependencies==actuator,cloud-eureka,data-jpa,h2,data-rest,web,devtools,lombok | tar -xzvf -
+
+. Create an API Gateway
+
+ http https://start.spring.io/starter.zip \
+ artifactId==api-gateway name==api-gateway baseDir==api-gateway \
+ dependencies==cloud-eureka,cloud-feign,data-rest,web,cloud-hystrix,lombok | tar -xzvf -
+
+. Install Java 11 with SDKMAN! https://sdkman.io/
+
+ sdk list java
+ sdk install java 11.0.2-open
+ sdk default java 11.0.2-open
+
+. Add `@EnableEurekaServer` and properties to set port and turn off discovery
+
+ server.port=8761
+ eureka.client.register-with-eureka=false
+
+. Add `@EnableDiscoveryClient` to main classes in `car-service` and `api-gateway`
+
+. Configure `car-service` to run on `8090` and set its name
+
+ server.port=8090
+ spring.application.name=car-service
+
+. Add an application name to the `api-gateway` project
+
+ spring.application.name=api-gateway
+
+. Create an API with Spring Boot and Spring Data [`boot-entity-lombok`, `boot-repo`, `boot-data`]
+
+. Show Lombok plugin is installed in IntelliJ
+
+. Configure gateway to enable resilient server-to-server communication
+
+ @EnableFeignClients
+ @EnableCircuitBreaker
+
+. Create a `Car` class with `@Data`
+
+. Create a feign client and cool car controller [`feign-client`, `cool-car-adapter`]
+
+. Add `@HystrixCommand` for failover [`hystrix-fallback`]
+
+. Start all servers, view Eureka server, and https://localhost:8080/cool-cars endpoint
+
+== Secure Java Microservices with OAuth 2.0 and OIDC
+
+. Add Okta Spring Boot starter to `api-gateway` and `car-service` [`okta-maven-boot`]
+
+
+ com.okta.spring
+ okta-spring-boot-starter
+ 1.4.0
+
+
+. Create a web app on Okta, use `http://localhost:8080/login/oauth2/code/okta` for redirect URI
+
+. Populate Okta properties in `application.properties`
+
+ okta.oauth2.issuer=$issuer
+ okta.oauth2.client-id=$clientId
+ okta.oauth2.client-secret=$clientSecret
+
+. Create a `SecurityConfiguration` class, and enable OAuth Login and a Resource Server [`ss-resource-config`]
+
+ @EnableWebSecurity
+ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests().anyRequest().authenticated()
+ .and()
+ .oauth2Login()
+ .and()
+ .oauth2ResourceServer().jwt();
+ // @formatter:on
+ }
+}
+
+. Enable Resource Server in the `car-service` application [`ss-resource-config`]
+
+. Create `UserFeignClientInterceptor` to add `Authorization` header in `api-gateway` [`feign-interceptor`]
+
+. Register interceptor as a bean [`feign-bean`]
+
+ @Bean
+ public RequestInterceptor getUserFeignClientInterceptor(OAuth2AuthorizedClientService clientService) {
+ return new UserFeignClientInterceptor(clientService);
+ }
+
+. Make Feign Spring Security-aware
+
+ feign.hystrix.enabled=true
+ hystrix.shareSecurityContext=true
+
+. Restart all apps and show with security enabled
+
+== Use Netflix Zuul for Routing
+
+. Add Zuul as a dependency and `@EnableZuulProxy`
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-zuul
+
+
+. Create an `AuthorizationHeaderFilter` to pass the access token to proxied routes [`zuul-auth-header`]
+
+. Register `AuthorizationHeaderFilter` filter as a bean [`zuul-bean`]
+
+ @Bean
+ public AuthorizationHeaderFilter authHeaderFilter(OAuth2AuthorizedClientService clientService) {
+ return new AuthorizationHeaderFilter(clientService);
+ }
+
+. Add Zuul routes for `/cars` and `/home` [`zuul-routes`]
+
+. Add `HomeController` to the `car-service` [`zuul-home`]
+
+. Restart and confirm `http://localhost:8080/cars` and `http://localhost:8080/home` routes work
+
+. Fin! 🏁
+
+== Learn More!
+
+. GitHub repo: https://github.com/oktadeveloper/java-microservices-examples
+
+. Blog post: https://developer.okta.com/blog/2019/05/22/java-microservices-spring-boot-spring-cloud
diff --git a/spring-boot+cloud/discovery-service/pom.xml b/spring-boot+cloud/discovery-service/pom.xml
index b7dfd6e..8a0c7f5 100755
--- a/spring-boot+cloud/discovery-service/pom.xml
+++ b/spring-boot+cloud/discovery-service/pom.xml
@@ -5,7 +5,7 @@
org.springframework.bootspring-boot-starter-parent
- 2.1.5.RELEASE
+ 2.2.5.RELEASEcom.example
@@ -16,7 +16,7 @@
11
- Greenwich.SR1
+ Hoxton.SR3
@@ -24,10 +24,6 @@
org.springframework.cloudspring-cloud-starter-netflix-eureka-server
-
- org.glassfish.jaxb
- jaxb-runtime
- org.springframework.bootspring-boot-starter-test
diff --git a/spring-cloud-gateway/api-gateway/.gitignore b/spring-cloud-gateway/api-gateway/.gitignore
new file mode 100755
index 0000000..a2a3040
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/.gitignore
@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-cloud-gateway/api-gateway/.mvn/wrapper/MavenWrapperDownloader.java b/spring-cloud-gateway/api-gateway/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100755
index 0000000..72308aa
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL =
+ "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: : " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.jar
new file mode 100755
index 0000000..01e6799
Binary files /dev/null and b/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 0000000..cd0d451
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/spring-cloud-gateway/api-gateway/mvnw b/spring-cloud-gateway/api-gateway/mvnw
new file mode 100755
index 0000000..8b9da3b
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-cloud-gateway/api-gateway/mvnw.cmd b/spring-cloud-gateway/api-gateway/mvnw.cmd
new file mode 100755
index 0000000..fef5a8f
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/spring-cloud-gateway/api-gateway/pom.xml b/spring-cloud-gateway/api-gateway/pom.xml
new file mode 100755
index 0000000..2484756
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/pom.xml
@@ -0,0 +1,103 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.7.RELEASE
+
+
+ com.example
+ api-gateway
+ 0.0.1-SNAPSHOT
+ api-gateway
+ Demo project for Spring Boot
+
+
+ 11
+ Greenwich.SR2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-hystrix
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+ com.okta.spring
+ okta-spring-boot-starter
+ 1.2.1
+
+
+ org.springframework.cloud
+ spring-cloud-security
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+ spring-boot:run
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ ossrh
+ false
+ true
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
diff --git a/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java b/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
new file mode 100755
index 0000000..1060f86
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
@@ -0,0 +1,83 @@
+package com.example.apigateway;
+
+import lombok.Data;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+
+import java.time.LocalDate;
+
+@EnableEurekaClient
+@SpringBootApplication
+public class ApiGatewayApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ApiGatewayApplication.class, args);
+ }
+
+ @Bean
+ public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
+ TokenRelayGatewayFilterFactory filterFactory) {
+ return builder.routes()
+ .route("car-service", r -> r.path("/cars")
+ .filters(f -> f.filter(filterFactory.apply()))
+ // .filters(f -> f.hystrix(c -> c.setName("carsFallback")
+ // .setFallbackUri("forward:/cars-fallback")))
+ .uri("lb://car-service"))
+ .build();
+ }
+
+ @Bean
+ @LoadBalanced
+ public WebClient.Builder loadBalancedWebClientBuilder() {
+ return WebClient.builder();
+ }
+}
+
+@Data
+class Car {
+ private String name;
+ private LocalDate releaseDate;
+}
+
+@RestController
+class FaveCarsController {
+
+ private final WebClient.Builder carClient;
+
+ public FaveCarsController(WebClient.Builder carClient) {
+ this.carClient = carClient;
+ }
+
+ @GetMapping("/fave-cars")
+ public Flux faveCars(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
+ return carClient.build().get().uri("lb://car-service/cars")
+ .header("Authorization", "Bearer " + authorizedClient.getAccessToken().getTokenValue())
+ .retrieve().bodyToFlux(Car.class)
+ .filter(this::isFavorite);
+ }
+
+ private boolean isFavorite(Car car) {
+ return car.getName().equals("ID. BUZZ");
+ }
+}
+
+@RestController
+class CarsFallback {
+
+ @GetMapping("/cars-fallback")
+ public Flux noCars() {
+ return Flux.empty();
+ }
+}
diff --git a/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/SecurityConfiguration.java b/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/SecurityConfiguration.java
new file mode 100644
index 0000000..adb18c1
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/src/main/java/com/example/apigateway/SecurityConfiguration.java
@@ -0,0 +1,45 @@
+package com.example.apigateway;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.reactive.CorsConfigurationSource;
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
+
+import java.util.List;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class SecurityConfiguration {
+
+ @Bean
+ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+ // @formatter:off
+ http
+ .authorizeExchange()
+ .anyExchange().authenticated()
+ .and()
+ .oauth2Login()
+ .and()
+ .oauth2ResourceServer()
+ .jwt();
+ return http.build();
+ // @formatter:on
+ }
+
+ @Bean
+ CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration corsConfig = new CorsConfiguration();
+ corsConfig.setAllowedOrigins(List.of("*"));
+ corsConfig.setMaxAge(3600L);
+ corsConfig.addAllowedMethod("*");
+ corsConfig.addAllowedHeader("*");
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", corsConfig);
+ return source;
+ }
+}
diff --git a/spring-cloud-gateway/api-gateway/src/main/resources/application.properties b/spring-cloud-gateway/api-gateway/src/main/resources/application.properties
new file mode 100755
index 0000000..ca3ec8d
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+spring.application.name=gateway
+okta.oauth2.issuer=https://dev-133320.okta.com/oauth2/default
+okta.oauth2.client-id=0oazig0adjD1PgAjO356
+okta.oauth2.client-secret=iNxF-y6iJACN2eeY8MO-bJ7IdhcSEjt1YXrrNfc0
+
+logging.level.root=WARN
+logging.level.org.springframework=INFO
diff --git a/spring-cloud-gateway/api-gateway/src/test/java/com/example/apigateway/ApiGatewayApplicationTests.java b/spring-cloud-gateway/api-gateway/src/test/java/com/example/apigateway/ApiGatewayApplicationTests.java
new file mode 100755
index 0000000..41e03c1
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/src/test/java/com/example/apigateway/ApiGatewayApplicationTests.java
@@ -0,0 +1,53 @@
+package com.example.apigateway;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {"spring.cloud.discovery.enabled = false"})
+public class ApiGatewayApplicationTests {
+
+ @Autowired
+ WebTestClient webTestClient;
+
+ @MockBean
+ ReactiveJwtDecoder jwtDecoder;
+
+ @Test
+ public void testCorsConfiguration() {
+ Jwt jwt = jwt();
+ when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
+ WebTestClient.ResponseSpec response = webTestClient.put().uri("/")
+ .headers(addJwt(jwt))
+ .header("Origin", "http://example.com")
+ .exchange();
+
+ response.expectHeader().valueEquals("Access-Control-Allow-Origin", "*");
+ }
+
+ private Jwt jwt() {
+ return new Jwt("token", null, null,
+ Map.of("alg", "none"), Map.of("sub", "dave"));
+ }
+
+ private Consumer addJwt(Jwt jwt) {
+ return headers -> headers.setBearerAuth(jwt.getTokenValue());
+ }
+}
diff --git a/spring-cloud-gateway/api-gateway/src/test/resources/logback.xml b/spring-cloud-gateway/api-gateway/src/test/resources/logback.xml
new file mode 100644
index 0000000..47e7acf
--- /dev/null
+++ b/spring-cloud-gateway/api-gateway/src/test/resources/logback.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/spring-cloud-gateway/car-service/.gitignore b/spring-cloud-gateway/car-service/.gitignore
new file mode 100755
index 0000000..a2a3040
--- /dev/null
+++ b/spring-cloud-gateway/car-service/.gitignore
@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-cloud-gateway/car-service/.mvn/wrapper/MavenWrapperDownloader.java b/spring-cloud-gateway/car-service/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100755
index 0000000..72308aa
--- /dev/null
+++ b/spring-cloud-gateway/car-service/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL =
+ "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: : " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100755
index 0000000..01e6799
Binary files /dev/null and b/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 0000000..cd0d451
--- /dev/null
+++ b/spring-cloud-gateway/car-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/spring-cloud-gateway/car-service/mvnw b/spring-cloud-gateway/car-service/mvnw
new file mode 100755
index 0000000..8b9da3b
--- /dev/null
+++ b/spring-cloud-gateway/car-service/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-cloud-gateway/car-service/mvnw.cmd b/spring-cloud-gateway/car-service/mvnw.cmd
new file mode 100755
index 0000000..fef5a8f
--- /dev/null
+++ b/spring-cloud-gateway/car-service/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/spring-cloud-gateway/car-service/pom.xml b/spring-cloud-gateway/car-service/pom.xml
new file mode 100755
index 0000000..7b53eff
--- /dev/null
+++ b/spring-cloud-gateway/car-service/pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.7.RELEASE
+
+
+ com.example
+ car-service
+ 0.0.1-SNAPSHOT
+ car-service
+ Demo project for Spring Boot
+
+
+ 11
+ Greenwich.SR2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ com.okta.spring
+ okta-spring-boot-starter
+ 1.2.1
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+ spring-boot:run
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/CarServiceApplication.java b/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/CarServiceApplication.java
new file mode 100755
index 0000000..3049c55
--- /dev/null
+++ b/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/CarServiceApplication.java
@@ -0,0 +1,101 @@
+package com.example.carservice;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Set;
+import java.util.UUID;
+
+@EnableEurekaClient
+@SpringBootApplication
+@Slf4j
+public class CarServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CarServiceApplication.class, args);
+ }
+
+ @Bean
+ ApplicationRunner init(CarRepository repository) {
+ // Electric VWs from https://www.vw.com/electric-concepts/
+ // Release dates from https://www.motor1.com/features/346407/volkswagen-id-price-on-sale/
+ Car ID = new Car(UUID.randomUUID(), "ID.", LocalDate.of(2019, Month.DECEMBER, 1));
+ Car ID_CROZZ = new Car(UUID.randomUUID(), "ID. CROZZ", LocalDate.of(2021, Month.MAY, 1));
+ Car ID_VIZZION = new Car(UUID.randomUUID(), "ID. VIZZION", LocalDate.of(2021, Month.DECEMBER, 1));
+ Car ID_BUZZ = new Car(UUID.randomUUID(), "ID. BUZZ", LocalDate.of(2021, Month.DECEMBER, 1));
+ Set vwConcepts = Set.of(ID, ID_BUZZ, ID_CROZZ, ID_VIZZION);
+
+ return args -> {
+ repository
+ .deleteAll()
+ .thenMany(
+ Flux
+ .just(vwConcepts)
+ .flatMap(repository::saveAll)
+ )
+ .thenMany(repository.findAll())
+ .subscribe(car -> log.info("saving " + car.toString()));
+ };
+ }
+}
+
+@Document
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+class Car {
+ @Id
+ private UUID id;
+ private String name;
+ private LocalDate releaseDate;
+}
+
+interface CarRepository extends ReactiveMongoRepository {
+}
+
+@RestController
+class CarController {
+
+ private CarRepository carRepository;
+
+ public CarController(CarRepository carRepository) {
+ this.carRepository = carRepository;
+ }
+
+ @PostMapping("/cars")
+ @ResponseStatus(HttpStatus.CREATED)
+ public Mono addCar(@RequestBody Car car) {
+ return carRepository.save(car);
+ }
+
+ @GetMapping("/cars")
+ public Flux getCars() {
+ return carRepository.findAll();
+ }
+
+ @DeleteMapping("/cars/{id}")
+ public Mono> deleteCar(@PathVariable("id") UUID id) {
+ return carRepository.findById(id)
+ .flatMap(car -> carRepository.delete(car)
+ .then(Mono.just(new ResponseEntity(HttpStatus.OK)))
+ )
+ .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
+ }
+}
diff --git a/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/SecurityConfiguration.java b/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/SecurityConfiguration.java
new file mode 100644
index 0000000..771165d
--- /dev/null
+++ b/spring-cloud-gateway/car-service/src/main/java/com/example/carservice/SecurityConfiguration.java
@@ -0,0 +1,29 @@
+package com.example.carservice;
+
+import com.okta.spring.boot.oauth.Okta;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class SecurityConfiguration {
+
+ @Bean
+ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+ // @formatter:off
+ http
+ .authorizeExchange()
+ .anyExchange().authenticated()
+ .and()
+ .oauth2ResourceServer()
+ .jwt();
+
+ Okta.configureResourceServer401ResponseBody(http);
+
+ return http.build();
+ // @formatter:on
+ }
+}
diff --git a/spring-cloud-gateway/car-service/src/main/resources/application.properties b/spring-cloud-gateway/car-service/src/main/resources/application.properties
new file mode 100755
index 0000000..128aca2
--- /dev/null
+++ b/spring-cloud-gateway/car-service/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+spring.application.name=car-service
+server.port=8081
+
+okta.oauth2.issuer=https://dev-133320.okta.com/oauth2/default
+okta.oauth2.client-id=0oazig0adjD1PgAjO356
+okta.oauth2.client-secret=iNxF-y6iJACN2eeY8MO-bJ7IdhcSEjt1YXrrNfc0
diff --git a/spring-cloud-gateway/car-service/src/test/java/com/example/carservice/CarServiceApplicationTests.java b/spring-cloud-gateway/car-service/src/test/java/com/example/carservice/CarServiceApplicationTests.java
new file mode 100755
index 0000000..827e8bf
--- /dev/null
+++ b/spring-cloud-gateway/car-service/src/test/java/com/example/carservice/CarServiceApplicationTests.java
@@ -0,0 +1,96 @@
+package com.example.carservice;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {"spring.cloud.discovery.enabled = false"})
+public class CarServiceApplicationTests {
+
+ @Autowired
+ CarRepository carRepository;
+
+ @Autowired
+ WebTestClient webTestClient;
+
+ @MockBean
+ ReactiveJwtDecoder jwtDecoder;
+
+ @Test
+ public void testAddCar() {
+ Car buggy = new Car(UUID.randomUUID(), "ID. BUGGY", LocalDate.of(2022, Month.DECEMBER, 1));
+
+ Jwt jwt = jwt();
+ when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
+
+ webTestClient.post().uri("/cars")
+ .contentType(MediaType.APPLICATION_JSON_UTF8)
+ .accept(MediaType.APPLICATION_JSON_UTF8)
+ .headers(addJwt(jwt))
+ .body(Mono.just(buggy), Car.class)
+ .exchange()
+ .expectStatus().isCreated()
+ .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
+ .expectBody()
+ .jsonPath("$.id").isNotEmpty()
+ .jsonPath("$.name").isEqualTo("ID. BUGGY");
+ }
+
+ @Test
+ public void testGetAllCars() {
+ Jwt jwt = jwt();
+ when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
+
+ webTestClient.get().uri("/cars")
+ .accept(MediaType.APPLICATION_JSON_UTF8)
+ .headers(addJwt(jwt))
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
+ .expectBodyList(Car.class);
+ }
+
+ @Test
+ public void testDeleteCar() {
+ Car buzzCargo = carRepository.save(new Car(UUID.randomUUID(), "ID. BUZZ CARGO",
+ LocalDate.of(2022, Month.DECEMBER, 2))).block();
+
+ Jwt jwt = jwt();
+ when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt));
+
+ webTestClient.delete()
+ .uri("/cars/{id}", Map.of("id", buzzCargo.getId()))
+ .headers(addJwt(jwt))
+ .exchange()
+ .expectStatus().isOk();
+ }
+
+ private Jwt jwt() {
+ return new Jwt("token", null, null,
+ Map.of("alg", "none"), Map.of("sub", "dave"));
+ }
+
+ private Consumer addJwt(Jwt jwt) {
+ return headers -> headers.setBearerAuth(jwt.getTokenValue());
+ }
+}
diff --git a/spring-cloud-gateway/demo.adoc b/spring-cloud-gateway/demo.adoc
new file mode 100644
index 0000000..fa8f1d8
--- /dev/null
+++ b/spring-cloud-gateway/demo.adoc
@@ -0,0 +1,127 @@
+:experimental:
+// Define unicode for Apple Command key.
+:commandkey: ⌘
+
+= Secure Reactive Microservices with Spring Cloud Gateway
+
+The brackets at the end of each step indicate the alias's or IntelliJ Live Templates to use. You can find the template definitions at https://github.com/mraible/idea-live-templates[mraible/idea-live-templates].
+
+== Create Eureka Server, Car Service, and API Gateway
+
+. Create a directory to hold all projects, e.g., `spring-cloud-gateway`
+
+. Create a Eureka Server
+
+ http https://start.spring.io/starter.zip javaVersion==11 artifactId==discovery-service \
+ name==eureka-service baseDir==discovery-service \
+ dependencies==cloud-eureka-server | tar -xzvf -
+
+. Add `@EnableEurekaServer` and properties to set port and turn off discovery
+
+ server.port=8761
+ eureka.client.register-with-eureka=false
+
+. Install Java 11 with SDKMAN! https://sdkman.io/
+
+ sdk list java
+ sdk install java 11.0.2-open
+ sdk default java 11.0.2-open
+
+. Start Eureka Server with `./mvnw spring-boot:run`
+
+. Create an API Gateway with Spring Cloud Gateway
+
+ http https://start.spring.io/starter.zip javaVersion==11 artifactId==api-gateway \
+ name==api-gateway baseDir==api-gateway \
+ dependencies==actuator,cloud-eureka,cloud-feign,cloud-gateway,cloud-hystrix,webflux,lombok | tar -xzvf -
+
+. Create a Reactive Microservice with Spring WebFlux
+
+ http https://start.spring.io/starter.zip javaVersion==11 artifactId==car-service \
+ name==car-service baseDir==car-service \
+ dependencies==actuator,cloud-eureka,webflux,data-mongodb-reactive,flapdoodle-mongo,lombok | tar -xzvf -
+
+. Add the application name and port to the `car-service` project's `application.properties`
+
+ spring.application.name=car-service
+ server.port=8081
+
+. Add Eureka registration, sample data initialization, and a reactive REST API to `CarServiceApplication.java` [`webflux-entity`, `webflux-repo`, `webflux-data-vw`, `webflux-controller-full`]
+
+. Show Lombok plugin is installed in IntelliJ
+
+. Run MongoDB or make `flapdoodle` a `compile` dependency
+
+. Create tests to verify REST endpoints with `WebTestClient` [`webflux-controller-test`]
+
+. Test with `./mvnw test`
+
+. Create an aggregator `pom.xml` to open all projects at once [`aggregator-pom`]
+
+. In `ApiGatewayApplication`, add `@EnableEurekaClient` and specify name in `application.properties`
+
+ spring.application.name=gateway
+
+. Create a `RouteLocator` bean in `ApiGatewayApplication` [`route-locator`]
+
+. Start all three apps and show `http :8080/cars` works
+
+== Add a Favorite Cars Endpoint
+
+. Add a load-balanced `WebClient.Builder` bean [`webclient-builder`]
+
+. Add a `Car` POJO with `name` and `releaseDate`
+
+. Add a `FaveCarsController` [`webflux-controller-fave`]
+
+. Show how `http://localhost:8080/fave-cars` only returns an ID Buzz
+
+== Add Failover with Hystrix
+
+Spring Cloud Gateway only supports Hystrix at this time. Spring Cloud deprecated direct support for Hystrix in favor of Spring Cloud Circuit Breaker. Unfortunately, this library hasn't had a GA release yet.
+
+. Add a filter to your `car-service` route [`hystrix-filter`]
+
+. Create a `CarsFallback` controller [`hystrix-controller`]
+
+. Restart your gateway and confirm `http://localhost:8080/cars` works, then shutdown car service to show fallback
+
+== Secure Java Microservices with OAuth 2.0 and OIDC
+
+. Run `./mvnw com.okta:okta-maven-plugin:setup` in the gateway project to create an Okta account and an OIDC app
+
+. Add the Okta Spring Boot starter and `spring-cloud-security` to the gateway's `pom.xml` [`okta-maven-boot`]
+
+. Verify `http://localhost:8080/fave-cars` requires login
+
+=== Make Your Gateway an OAuth 2.0 Resource Server
+
+. Add `SecurityConfiguration` class [`ss-config`]
+
+. Add a `CorsWebFilter` bean for SPAs [`webflux-cors-filter`]
+
+. Test your gateway and confirm CORS is working [`webflux-cors-test`]
+
+=== Secure Gateway to Microservice Communication
+
+. Add the Okta Spring Boot starter to `car-service/pom.xml`
+
+. Copy the `okta.*` properties from the gateway's `application.properties` to the car service
+
+. Create a `SecurityConfiguration` to make the app an OAuth 2.0 resource server [`ss-resource-webflux`]
+
+. Restart car service and show `http :8081/cars` returns a 401
+
+. Modify `CarServiceApplicationTests.java` to add JWT access tokens to each request [`webflux-controller-test-jwt`]
+
+. In `ApiGatewayApplication.java`, add a filter that applies the `TokenRelayGatewayFilterFactory` from Spring Cloud Security [`route-locator-token`]
+
+. Restart the API Gateway and confirm `http://localhost:8080/cars` works
+
+. Congrats! 🏁
+
+== Learn More!
+
+. GitHub repo: https://github.com/oktadeveloper/java-microservices-examples
+
+. Blog post: https://developer.okta.com/blog/2019/08/28/reactive-microservices-spring-cloud-gateway
diff --git a/spring-cloud-gateway/discovery-service/.gitignore b/spring-cloud-gateway/discovery-service/.gitignore
new file mode 100755
index 0000000..a2a3040
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/.gitignore
@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-cloud-gateway/discovery-service/.mvn/wrapper/MavenWrapperDownloader.java b/spring-cloud-gateway/discovery-service/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100755
index 0000000..72308aa
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL =
+ "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: : " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100755
index 0000000..01e6799
Binary files /dev/null and b/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 0000000..cd0d451
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/spring-cloud-gateway/discovery-service/mvnw b/spring-cloud-gateway/discovery-service/mvnw
new file mode 100755
index 0000000..8b9da3b
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-cloud-gateway/discovery-service/mvnw.cmd b/spring-cloud-gateway/discovery-service/mvnw.cmd
new file mode 100755
index 0000000..fef5a8f
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/spring-cloud-gateway/discovery-service/pom.xml b/spring-cloud-gateway/discovery-service/pom.xml
new file mode 100755
index 0000000..19224bf
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.7.RELEASE
+
+
+ com.example
+ discovery-service
+ 0.0.1-SNAPSHOT
+ eureka-service
+ Demo project for Spring Boot
+
+
+ 11
+ Greenwich.SR2
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-server
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+ spring-boot:run
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-cloud-gateway/discovery-service/src/main/java/com/example/discoveryservice/EurekaServiceApplication.java b/spring-cloud-gateway/discovery-service/src/main/java/com/example/discoveryservice/EurekaServiceApplication.java
new file mode 100755
index 0000000..eac1675
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/src/main/java/com/example/discoveryservice/EurekaServiceApplication.java
@@ -0,0 +1,15 @@
+package com.example.discoveryservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
+
+@EnableEurekaServer
+@SpringBootApplication
+public class EurekaServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(EurekaServiceApplication.class, args);
+ }
+
+}
diff --git a/spring-cloud-gateway/discovery-service/src/main/resources/application.properties b/spring-cloud-gateway/discovery-service/src/main/resources/application.properties
new file mode 100755
index 0000000..7edbea4
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+server.port=8761
+eureka.client.register-with-eureka=false
diff --git a/spring-cloud-gateway/discovery-service/src/test/java/com/example/discoveryservice/EurekaServiceApplicationTests.java b/spring-cloud-gateway/discovery-service/src/test/java/com/example/discoveryservice/EurekaServiceApplicationTests.java
new file mode 100755
index 0000000..b3dbd71
--- /dev/null
+++ b/spring-cloud-gateway/discovery-service/src/test/java/com/example/discoveryservice/EurekaServiceApplicationTests.java
@@ -0,0 +1,16 @@
+package com.example.discoveryservice;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class EurekaServiceApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/spring-cloud-gateway/pom.xml b/spring-cloud-gateway/pom.xml
new file mode 100644
index 0000000..1aecb20
--- /dev/null
+++ b/spring-cloud-gateway/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+ com.okta.developer
+ reactive-parent
+ 1.0.0-SNAPSHOT
+ pom
+ reactive-parent
+
+ discovery-service
+ car-service
+ api-gateway
+
+