configParameters = super.getConfigParameters();
+ swaggerUrls.add(new SwaggerUrl(applicationName, (String) configParameters.get(URL_PROPERTY), WordUtils.capitalize(applicationName)));
+ configParameters.put(URLS_PROPERTY, swaggerUrls);
+ return configParameters;
+ }
+
+ private SwaggerUrl toSwaggerUrl(String serviceName) {
+ return new SwaggerUrl(serviceName, SERVICE_API_DOCS_URL_PREFIX + serviceName, WordUtils.capitalize(serviceName));
+ }
+
+}
diff --git a/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionResourceNotFoundException.java b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionResourceNotFoundException.java
new file mode 100644
index 0000000..cf6a5da
--- /dev/null
+++ b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionResourceNotFoundException.java
@@ -0,0 +1,11 @@
+package com.tungstun.gateway.swagger;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Exception to be thrown if a requested service OpenApi Definition does not exist or something went wrong during it's retrieval.
+ */
+@ResponseStatus(value = HttpStatus.NOT_FOUND)
+public class ServiceDefinitionResourceNotFoundException extends RuntimeException {
+}
diff --git a/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsContext.java b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsContext.java
new file mode 100644
index 0000000..5d4120a
--- /dev/null
+++ b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsContext.java
@@ -0,0 +1,40 @@
+package com.tungstun.gateway.swagger;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Code adapted from https://github.com/GnanaJeyam/microservice-patterns
+ *
+ * Singleton class that stores an In-Memory map with service id's paired with their OpenApi Definition JSON String
+ */
+@Component
+@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
+public class ServiceDefinitionsContext {
+ private ConcurrentMap serviceDefinitions;
+
+ private ServiceDefinitionsContext() {
+ this.serviceDefinitions = new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Changes the {@code serviceDefinitions} ConcurrentMap to the provided ConcurrentMap if it is not {@code null}.
+ */
+ public void updateServiceDefinitions(ConcurrentMap map) {
+ if (map != null) serviceDefinitions = map;
+ }
+
+ public String getSwaggerDefinition(String serviceId) {
+ return this.serviceDefinitions.get(serviceId);
+ }
+
+ public List getServiceIds() {
+ return new ArrayList<>(serviceDefinitions.keySet());
+ }
+}
diff --git a/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsUpdater.java b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsUpdater.java
new file mode 100644
index 0000000..508e767
--- /dev/null
+++ b/gateway/src/main/java/com/tungstun/gateway/swagger/ServiceDefinitionsUpdater.java
@@ -0,0 +1,95 @@
+package com.tungstun.gateway.swagger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.graalvm.collections.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * Code adapted from https://github.com/GnanaJeyam/microservice-patterns
+ *
+ * Class that periodically updates the {@code ServiceDefinitionContext}.
+ */
+@Component
+public class ServiceDefinitionsUpdater {
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceDefinitionsUpdater.class);
+ private static final String DEFAULT_SWAGGER_URL = "/api/v3/api-docs";
+
+ private final ServiceDefinitionsContext definitionContext;
+ private final DiscoveryClient discoveryClient;
+ private final RestTemplate template;
+
+ @Value("${spring.application.name}")
+ private String applicationName;
+
+ public ServiceDefinitionsUpdater(ServiceDefinitionsContext definitionContext, DiscoveryClient discoveryClient) {
+ this.definitionContext = definitionContext;
+ this.discoveryClient = discoveryClient;
+ this.template = new RestTemplate();
+ }
+
+ /**
+ * Gets all running services from the service registry, polls their OpenApi Definition and updates the {@code ServiceDefinitionContext} with them.
+ */
+ @Scheduled(fixedDelayString = "${swagger.config.refreshrate}")
+ public void refreshOpenApiDefinitions() {
+ LOG.info("Starting Service Definitions Context refresh");
+
+ ConcurrentMap serviceDocumentations = discoveryClient.getServices()
+ .parallelStream()
+ .filter(serviceId -> !serviceId.equals(applicationName))
+ .map(serviceId -> Pair.create(serviceId, getServiceDefinition(serviceId)))
+ .filter(pair -> pair.getRight().isPresent())
+ .collect(Collectors.toConcurrentMap(Pair::getLeft, pair -> pair.getRight().get()));
+ definitionContext.updateServiceDefinitions(serviceDocumentations);
+
+ LOG.info("Service Definitions Context Refreshed for at : {}", LocalDateTime.now());
+ }
+
+ /**
+ * Gets a running service instance's URL and polls the OpenApi Definition from it.
+ */
+ private Optional getServiceDefinition(String serviceId) {
+ List serviceInstances = discoveryClient.getInstances(serviceId);
+ if (serviceInstances == null || serviceInstances.isEmpty()) {
+ LOG.info("No instances available for service : {} ", serviceId);
+ return Optional.empty();
+ }
+
+ String swaggerURL = serviceInstances.get(0).getUri() + DEFAULT_SWAGGER_URL;
+ Optional jsonData = pollOpenApiDefinition(swaggerURL, serviceId);
+ if (jsonData.isEmpty()) {
+ LOG.error("Skipping Definition Refresh for service : {}", serviceId);
+ }
+
+ return jsonData;
+ }
+
+ /**
+ * Polls the OpenApi Definition JSON from the provided url.
+ */
+ private Optional pollOpenApiDefinition(String url, String serviceName) {
+ try {
+ Object data = template.getForObject(url, Object.class);
+ String json = new ObjectMapper().writeValueAsString(data);
+ return Optional.of(json);
+ } catch (RestClientException | JsonProcessingException ex) {
+ LOG.error("Error while getting service definition for service : {}. Error : {} ", serviceName, ex.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/gateway/src/main/java/com/tungstun/gateway/swagger/SwaggerConfig.java b/gateway/src/main/java/com/tungstun/gateway/swagger/SwaggerConfig.java
new file mode 100644
index 0000000..ce32eab
--- /dev/null
+++ b/gateway/src/main/java/com/tungstun/gateway/swagger/SwaggerConfig.java
@@ -0,0 +1,77 @@
+package com.tungstun.gateway.swagger;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.SpecVersion;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import org.apache.http.entity.ContentType;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import java.util.Optional;
+
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+
+@Configuration
+public class SwaggerConfig {
+ private final ServiceDefinitionsContext definitionContext;
+
+ public SwaggerConfig(ServiceDefinitionsContext definitionContext) {
+ this.definitionContext = definitionContext;
+ }
+
+ @Bean
+ public OpenAPI gatewayOpenApi(@Value("${GATEWAY_URL:}") String gatewayUrl) {
+ OpenAPI openApi = new OpenAPI(SpecVersion.V31)
+ .info(new Info()
+ .title("bartap Backend Api - Gateway")
+ .description("API Gateway of the bartap Backend API microservice cluster containing Centralized API Documentations.")
+ .version("1.0")
+ .contact(new Contact()
+ .name("Tungstun")
+ .url("https://github.com/tungstun-ict")
+ .email("jort@tungstun.nl")))
+ .schemaRequirement("Bearer", new SecurityScheme()
+ .name("Bearer")
+ .description("Authorization using Bearer JWT")
+ .type(SecurityScheme.Type.HTTP)
+ .in(SecurityScheme.In.HEADER)
+ .scheme("bearer")
+ .bearerFormat("JWT"));
+
+ if (gatewayUrl != null && !gatewayUrl.isEmpty()) {
+ openApi.addServersItem(new Server()
+ .description("Security service")
+ .url(gatewayUrl)
+ );
+ }
+
+ return openApi;
+ }
+
+ /**
+ * Router Function for Swagger UI to get the cached OpenApi Definition of running registered services
+ */
+ @Bean
+ @Operation(hidden = true)
+ public RouterFunction serviceApiDocs() {
+ return RouterFunctions.route(GET("/v3/api-docs/services/{serviceName}"), (ServerRequest req) -> {
+ String service = req.pathVariable("serviceName");
+ String swaggerDocs = Optional.ofNullable(definitionContext.getSwaggerDefinition(service))
+ .orElseThrow(ServiceDefinitionResourceNotFoundException::new);
+
+ return ServerResponse.ok()
+ .header("Content-Type", ContentType.APPLICATION_JSON.toString())
+ .body(BodyInserters.fromValue(swaggerDocs));
+ });
+ }
+}
diff --git a/gateway/src/main/resources/application-ci.properties b/gateway/src/main/resources/application-ci.properties
index 4900823..e69de29 100644
--- a/gateway/src/main/resources/application-ci.properties
+++ b/gateway/src/main/resources/application-ci.properties
@@ -1,6 +0,0 @@
-# Service Routes
-BARTAP_SECURITY_URL=http://localhost:8081/api
-BARTAP_CORE_URL=http://localhost:8082/api
-BARTAP_PERSON_URL=http://localhost:8083/api
-BARTAP_PRODUCT_URL=http://localhost:8084/api
-BARTAP_ORDER_URL=http://localhost:8085/api
\ No newline at end of file
diff --git a/gateway/src/main/resources/application-dev.properties b/gateway/src/main/resources/application-dev.properties
index 4900823..8b35d57 100644
--- a/gateway/src/main/resources/application-dev.properties
+++ b/gateway/src/main/resources/application-dev.properties
@@ -1,6 +1,2 @@
-# Service Routes
-BARTAP_SECURITY_URL=http://localhost:8081/api
-BARTAP_CORE_URL=http://localhost:8082/api
-BARTAP_PERSON_URL=http://localhost:8083/api
-BARTAP_PRODUCT_URL=http://localhost:8084/api
-BARTAP_ORDER_URL=http://localhost:8085/api
\ No newline at end of file
+# Springdoc OpenAPI / Swagger
+swagger.config.refreshrate=30000
diff --git a/gateway/src/main/resources/application-prod.properties b/gateway/src/main/resources/application-prod.properties
index d939517..e288bf3 100644
--- a/gateway/src/main/resources/application-prod.properties
+++ b/gateway/src/main/resources/application-prod.properties
@@ -1,9 +1,2 @@
# Locale and timezones
com.tungstun.bartap.locale.timezone=${TIMEZONE:Europe/Amsterdam}
-
-# Service Routes
-BARTAP_SECURITY_URL=http://localhost:8081/api
-BARTAP_CORE_URL=http://localhost:8082/api
-BARTAP_PERSON_URL=http://localhost:8083/api
-BARTAP_PRODUCT_URL=http://localhost:8084/api
-BARTAP_ORDER_URL=http://localhost:8085/api
\ No newline at end of file
diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties
index f5aa00c..1d0e531 100644
--- a/gateway/src/main/resources/application.properties
+++ b/gateway/src/main/resources/application.properties
@@ -1,18 +1,23 @@
#Standard configurations
-spring.application.name=bartap-gateway
+spring.application.name=gateway
spring.profiles.active=dev
server.port=8081
-server.servlet.context-path=/api
+#server.servlet.context-path=/api
+spring.webflux.base-path=/api
spring.main.web-application-type=reactive
spring.cloud.gateway.enabled=true
-
# Service Routes
-com.tungstun.bartap.routes.security=${BARTAP_SECURITY_URL:http://localhost:8084/api}
-com.tungstun.bartap.routes.core=${BARTAP_CORE_URL:http://localhost:8082/api}
-com.tungstun.bartap.routes.bill=${BARTAP_BILL_URL:http://localhost:8083/api}
-com.tungstun.bartap.routes.person=${BARTAP_PERSON_URL:http://localhost:8084/api}
-com.tungstun.bartap.routes.product=${BARTAP_PRODUCT_URL:http://localhost:8085/api}
-com.tungstun.bartap.routes.order=${BARTAP_ORDER_URL:http://localhost:8086/api}
-
-
-
+com.tungstun.services.security=lb://BARTAP-SECURITY/api
+com.tungstun.services.core=lb://BARTAP-CORE/api
+com.tungstun.services.bill=lb://BARTAP-BILL/api
+com.tungstun.services.person=lb://BARTAP-PERSON/api
+com.tungstun.services.product=lb://BARTAP-PRODUCT/api
+com.tungstun.services.order=lb://BARTAP-ORDER/api
+# Eureka Registry
+eureka.client.service-url.defaultZone=http://localhost:8761/eureka
+eureka.client.fetch-registry=true
+eureka.client.register-with-eureka=true
+# Springdoc OpenAPI / Swagger
+springdoc.swagger-ui.path=/swagger
+# 5 minutes refresh delay
+swagger.config.refreshrate=300000
diff --git a/person/build.gradle b/person/build.gradle
index f22f283..62c3517 100644
--- a/person/build.gradle
+++ b/person/build.gradle
@@ -12,4 +12,14 @@ repositories {
dependencies {
implementation project(':common')
annotationProcessor project(':common')
+
+// Spring dependencies
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+ testImplementation 'org.springframework.security:spring-security-test'
+
+// Eureka Service Registry
+ implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:3.1.3'
+
+// Open Api UI dependency
+ implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
}
diff --git a/person/src/main/java/com/tungstun/person/PersonApplication.java b/person/src/main/java/com/tungstun/person/PersonApplication.java
index 7dba5cb..ad076fb 100644
--- a/person/src/main/java/com/tungstun/person/PersonApplication.java
+++ b/person/src/main/java/com/tungstun/person/PersonApplication.java
@@ -2,12 +2,14 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.kafka.annotation.EnableKafka;
@SpringBootApplication
@ComponentScan("com.tungstun")
@EnableKafka
+@EnableEurekaClient
public class PersonApplication {
public static void main(String[] args) {
SpringApplication.run(PersonApplication.class, args);
diff --git a/person/src/main/java/com/tungstun/person/config/SwaggerConfig.java b/person/src/main/java/com/tungstun/person/config/SwaggerConfig.java
index 37a9d33..28bf892 100644
--- a/person/src/main/java/com/tungstun/person/config/SwaggerConfig.java
+++ b/person/src/main/java/com/tungstun/person/config/SwaggerConfig.java
@@ -1,26 +1,45 @@
package com.tungstun.person.config;
-import io.swagger.v3.oas.annotations.OpenAPIDefinition;
-import io.swagger.v3.oas.annotations.info.Contact;
-import io.swagger.v3.oas.annotations.info.Info;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.models.annotations.OpenAPI31;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.SpecVersion;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
-@OpenAPI31
-@OpenAPIDefinition(
- info = @Info(
- title = "bartap Backend Api - Person",
- description = "Person API of the bartap Backend API microservice cluster containing person functionality",
- version = "1.0",
- contact = @Contact(
- name = "Tungstun",
- url = "https://github.com/tungstun-ict",
- email = "jort@tungstun.nl"
- )
- ),
- tags = {
- @Tag(name = "Person", description = "Functionality based around the Person")
- }
-)
+@Configuration
public class SwaggerConfig {
+ @Bean
+ public OpenAPI gatewayOpenApi(@Value("${GATEWAY_URL:}") String gatewayUrl) {
+ OpenAPI openApi = new OpenAPI(SpecVersion.V31)
+ .info(new Info()
+ .title("bartap Backend Api - Person")
+ .description("Person API of the bartap Backend API microservice cluster containing person functionality")
+ .version("1.0")
+ .contact(new Contact()
+ .name("Tungstun")
+ .url("https://github.com/tungstun-ict")
+ .email("jort@tungstun.nl")))
+ .addTagsItem(new Tag().name("Person").description("Functionality based around the Person"))
+ .schemaRequirement("Bearer", new SecurityScheme()
+ .name("Bearer")
+ .description("Authorization using Bearer JWT")
+ .type(SecurityScheme.Type.HTTP)
+ .in(SecurityScheme.In.HEADER)
+ .scheme("bearer")
+ .bearerFormat("JWT"));
+
+ if (gatewayUrl != null) {
+ openApi.addServersItem(new Server()
+ .description("Gateway Url")
+ .url(gatewayUrl)
+ );
+ }
+
+ return openApi;
+ }
}
diff --git a/person/src/main/resources/application-dev.properties b/person/src/main/resources/application-dev.properties
index 965a2e6..88f3129 100644
--- a/person/src/main/resources/application-dev.properties
+++ b/person/src/main/resources/application-dev.properties
@@ -4,3 +4,5 @@ JWT_ISSUER=bartap-security-service
#Database properties and variables
JDBC_DATABASE_URL=jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
DRIVER_CLASS_NAME=org.h2.Driver
+#Gateway url
+GATEWAY_URL=http://localhost:8081${server.servlet.context-path}
diff --git a/person/src/main/resources/application.properties b/person/src/main/resources/application.properties
index 74ef4b3..ffd6554 100644
--- a/person/src/main/resources/application.properties
+++ b/person/src/main/resources/application.properties
@@ -1,7 +1,8 @@
-spring.application.name=bartap-product
+spring.application.name=person
spring.profiles.active=dev
-server.port=8085
+server.port=8086
server.servlet.context-path=/api
+
#JWT variables
com.tungstun.bartap.security.jwt.jwtSecret=${JWT_SECRET}
com.tungstun.bartap.security.jwt.jwtIssuer=${JWT_ISSUER}
@@ -15,3 +16,7 @@ spring.datasource.password=${JDBC_DATABASE_PASSWORD}
# Kafka
spring.kafka.bootstrap-servers=${BOOTSTRAP_SERVERS:localhost:9092}
spring.kafka.consumer.auto-offset-reset=earliest
+# Eureka Registry
+eureka.client.service-url.defaultZone=http://localhost:8761/eureka
+eureka.client.fetch-registry=true
+eureka.client.register-with-eureka=true
diff --git a/product/build.gradle b/product/build.gradle
index aa86021..ea9a303 100644
--- a/product/build.gradle
+++ b/product/build.gradle
@@ -13,6 +13,16 @@ dependencies {
implementation project(':common')
annotationProcessor project(':common')
- implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
+// Spring dependencies
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+ testImplementation 'org.springframework.security:spring-security-test'
+
+// Apache Commons Text
implementation 'org.apache.commons:commons-text:1.9'
+
+// Eureka Service Registry
+ implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:3.1.3'
+
+// Open Api UI dependency
+ implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
}
diff --git a/product/src/main/java/com/tungstun/product/ProductApplication.java b/product/src/main/java/com/tungstun/product/ProductApplication.java
index 1e15569..0fe5d51 100644
--- a/product/src/main/java/com/tungstun/product/ProductApplication.java
+++ b/product/src/main/java/com/tungstun/product/ProductApplication.java
@@ -2,10 +2,14 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
+import org.springframework.kafka.annotation.EnableKafka;
@SpringBootApplication
@ComponentScan("com.tungstun")
+@EnableKafka
+@EnableEurekaClient
public class ProductApplication {
public static void main(String[] args) {
diff --git a/product/src/main/java/com/tungstun/product/config/SwaggerConfig.java b/product/src/main/java/com/tungstun/product/config/SwaggerConfig.java
index 2f84971..7b0e1d2 100644
--- a/product/src/main/java/com/tungstun/product/config/SwaggerConfig.java
+++ b/product/src/main/java/com/tungstun/product/config/SwaggerConfig.java
@@ -1,27 +1,46 @@
package com.tungstun.product.config;
-import io.swagger.v3.oas.annotations.OpenAPIDefinition;
-import io.swagger.v3.oas.annotations.info.Contact;
-import io.swagger.v3.oas.annotations.info.Info;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.models.annotations.OpenAPI31;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.SpecVersion;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
-@OpenAPI31
-@OpenAPIDefinition(
- info = @Info(
- title = "bartap Backend Api - Product",
- description = "Product API of the bartap Backend API microservice cluster containing product and category functionality",
- version = "1.0",
- contact = @Contact(
- name = "Tungstun",
- url = "https://github.com/tungstun-ict",
- email = "jort@tungstun.nl"
- )
- ),
- tags = {
- @Tag(name = "Category", description = "Functionality based around the Category"),
- @Tag(name = "Product", description = "Functionality based around the Product")
- }
-)
+@Configuration
public class SwaggerConfig {
+ @Bean
+ public OpenAPI gatewayOpenApi(@Value("${GATEWAY_URL:}") String gatewayUrl) {
+ OpenAPI openApi = new OpenAPI(SpecVersion.V31)
+ .info(new Info()
+ .title("bartap Backend Api - Product")
+ .description("Product API of the bartap Backend API microservice cluster containing product and category functionality")
+ .version("1.0")
+ .contact(new Contact()
+ .name("Tungstun")
+ .url("https://github.com/tungstun-ict")
+ .email("jort@tungstun.nl")))
+ .addTagsItem(new Tag().name("Category").description("Functionality based around the Category"))
+ .addTagsItem(new Tag().name("Product").description("Functionality based around the Product"))
+ .schemaRequirement("Bearer", new SecurityScheme()
+ .name("Bearer")
+ .description("Authorization using Bearer JWT")
+ .type(SecurityScheme.Type.HTTP)
+ .in(SecurityScheme.In.HEADER)
+ .scheme("bearer")
+ .bearerFormat("JWT"));
+
+ if (gatewayUrl != null) {
+ openApi.addServersItem(new Server()
+ .description("Gateway Url")
+ .url(gatewayUrl)
+ );
+ }
+
+ return openApi;
+ }
}
diff --git a/product/src/main/resources/application-dev.properties b/product/src/main/resources/application-dev.properties
index de1262c..88f3129 100644
--- a/product/src/main/resources/application-dev.properties
+++ b/product/src/main/resources/application-dev.properties
@@ -1,7 +1,8 @@
#JWT variables
JWT_SECRET=lets-make-a-bar-application-and-use-this-as-a-session-token-secret
JWT_ISSUER=bartap-security-service
-
#Database properties and variables
JDBC_DATABASE_URL=jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
DRIVER_CLASS_NAME=org.h2.Driver
+#Gateway url
+GATEWAY_URL=http://localhost:8081${server.servlet.context-path}
diff --git a/product/src/main/resources/application.properties b/product/src/main/resources/application.properties
index 378f9fa..56cfbbf 100644
--- a/product/src/main/resources/application.properties
+++ b/product/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-spring.application.name=bartap-product
+spring.application.name=product
spring.profiles.active=dev
server.port=8085
server.servlet.context-path=/api
@@ -14,7 +14,10 @@ spring.datasource.driverClassName=${DRIVER_CLASS_NAME}
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
-
# Kafka
spring.kafka.bootstrap-servers=${BOOTSTRAP_SERVERS:localhost:9092}
spring.kafka.consumer.auto-offset-reset=earliest
+# Eureka Registry
+eureka.client.service-url.defaultZone=http://localhost:8761/eureka
+eureka.client.fetch-registry=true
+eureka.client.register-with-eureka=true
diff --git a/security/build.gradle b/security/build.gradle
index fb564b8..974f21f 100644
--- a/security/build.gradle
+++ b/security/build.gradle
@@ -14,8 +14,18 @@ dependencies {
implementation project(':common')
annotationProcessor project(':common')
- implementation 'com.auth0:java-jwt:3.19.2'
+// Spring dependencies
+ implementation 'org.springframework.boot:spring-boot-starter-security'
+ testImplementation 'org.springframework.security:spring-security-test'
+
+// Eureka Service Registry
+ implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:3.1.3'
+
+// Open Api UI dependency
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
+
+// Auth0 JWT
+ implementation 'com.auth0:java-jwt:3.19.2'
}
diff --git a/security/src/main/java/com/tungstun/security/SecurityApplication.java b/security/src/main/java/com/tungstun/security/SecurityApplication.java
index 24209b6..975b406 100644
--- a/security/src/main/java/com/tungstun/security/SecurityApplication.java
+++ b/security/src/main/java/com/tungstun/security/SecurityApplication.java
@@ -2,12 +2,14 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.kafka.annotation.EnableKafka;
@SpringBootApplication
@ComponentScan("com.tungstun")
@EnableKafka
+@EnableEurekaClient
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
diff --git a/security/src/main/java/com/tungstun/security/config/SwaggerConfig.java b/security/src/main/java/com/tungstun/security/config/SwaggerConfig.java
index 50bd90e..65208ed 100644
--- a/security/src/main/java/com/tungstun/security/config/SwaggerConfig.java
+++ b/security/src/main/java/com/tungstun/security/config/SwaggerConfig.java
@@ -1,28 +1,47 @@
package com.tungstun.security.config;
-import io.swagger.v3.oas.annotations.OpenAPIDefinition;
-import io.swagger.v3.oas.annotations.info.Contact;
-import io.swagger.v3.oas.annotations.info.Info;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.models.annotations.OpenAPI31;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.SpecVersion;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
-@OpenAPI31
-@OpenAPIDefinition(
- info = @Info(
- title = "bartap Backend Api - Security",
- description = "Security API of the bartap Backend API microservice cluster containing user and authorization functionality",
- version = "1.0",
- contact = @Contact(
- name = "Tungstun",
- url = "https://github.com/tungstun-ict",
- email = "jort@tungstun.nl"
- )
- ),
- tags = {
- @Tag(name = "User", description = "Functionality based around the user"),
- @Tag(name = "Authentication", description = "Functionality based around the authentication"),
- @Tag(name = "Authorization", description = "Functionality based around the authorization"),
- }
-)
+@Configuration
public class SwaggerConfig {
+ @Bean
+ public OpenAPI gatewayOpenApi(@Value("${GATEWAY_URL:}") String gatewayUrl) {
+ OpenAPI openApi = new OpenAPI(SpecVersion.V31)
+ .info(new Info()
+ .title("bartap Backend Api - Security")
+ .description("Security API of the bartap Backend API microservice cluster containing user and authorization functionality")
+ .version("1.0")
+ .contact(new Contact()
+ .name("Tungstun")
+ .url("https://github.com/tungstun-ict")
+ .email("jort@tungstun.nl")))
+ .addTagsItem(new Tag().name("User").description("Functionality based around the user"))
+ .addTagsItem(new Tag().name("Authentication").description("Functionality based around the authentication"))
+ .addTagsItem(new Tag().name("Authorization").description("Functionality based around the authorization"))
+ .schemaRequirement("Bearer", new SecurityScheme()
+ .name("Bearer")
+ .description("Authorization using Bearer JWT")
+ .type(SecurityScheme.Type.HTTP)
+ .in(SecurityScheme.In.HEADER)
+ .scheme("bearer")
+ .bearerFormat("JWT"));
+
+ if (gatewayUrl != null) {
+ openApi.addServersItem(new Server()
+ .description("Gateway Url")
+ .url(gatewayUrl)
+ );
+ }
+
+ return openApi;
+ }
}
diff --git a/security/src/main/java/com/tungstun/security/domain/user/User.java b/security/src/main/java/com/tungstun/security/domain/user/User.java
index 49018d0..3db599c 100644
--- a/security/src/main/java/com/tungstun/security/domain/user/User.java
+++ b/security/src/main/java/com/tungstun/security/domain/user/User.java
@@ -54,13 +54,13 @@ public User(String username, String password, String mail, String firstName, Str
}
public void canAuthenticate() {
- if (isAccountNonExpired())
+ if (!isAccountNonExpired())
throw new CannotAuthenticateException("Account expired. An expired account cannot be authenticated.");
- if (isAccountNonLocked())
+ if (!isAccountNonLocked())
throw new CannotAuthenticateException("Account locked. A locked account cannot be authenticated.");
- if (isCredentialsNonExpired())
+ if (!isCredentialsNonExpired())
throw new CannotAuthenticateException("Account credentials expired. Expired credentials prevent authentication.");
- if (isEnabled())
+ if (!isEnabled())
throw new CannotAuthenticateException("Account disabled. A disabled account cannot be authenticated.");
}
diff --git a/security/src/main/resources/application-dev.properties b/security/src/main/resources/application-dev.properties
index 7c0c613..12eaa17 100644
--- a/security/src/main/resources/application-dev.properties
+++ b/security/src/main/resources/application-dev.properties
@@ -4,7 +4,8 @@ JWT_EXPIRATION_DATE_IN_MS=300000
JWT_REFRESH_EXPIRATION_DATE_IN_MS=3000000
JWT_ISSUER=bartap-security-service
JWT_AUDIENCE=bartap-security-service
-
#Database properties and variables
JDBC_DATABASE_URL=jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
DRIVER_CLASS_NAME=org.h2.Driver
+#Gateway url
+GATEWAY_URL=http://localhost:8081${server.servlet.context-path}
diff --git a/security/src/main/resources/application.properties b/security/src/main/resources/application.properties
index 8d47762..fbe9215 100644
--- a/security/src/main/resources/application.properties
+++ b/security/src/main/resources/application.properties
@@ -1,5 +1,5 @@
#Standard configurations
-spring.application.name=bartap-security
+spring.application.name=security
spring.profiles.active=dev
server.port=8084
server.servlet.context-path=/api
@@ -17,7 +17,10 @@ spring.datasource.driverClassName=${DRIVER_CLASS_NAME}
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
-
# Kafka
spring.kafka.bootstrap-servers=${BOOTSTRAP_SERVERS:localhost:9092}
-spring.kafka.consumer.auto-offset-reset=earliest
\ No newline at end of file
+spring.kafka.consumer.auto-offset-reset=earliest
+# Eureka Registry
+eureka.client.service-url.defaultZone=http://localhost:8761/eureka
+eureka.client.fetch-registry=true
+eureka.client.register-with-eureka=true
diff --git a/settings.gradle b/settings.gradle
index 0484f89..8db7ac6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
rootProject.name = 'bartap'
-include('bill', 'common', 'core', 'gateway', 'person', 'product', 'security')
+include('bill', 'common', 'core', 'eureka-server', 'gateway', 'person', 'product', 'security')