Centralized Swagger/OpenAPI UI for all the different microservices on a single swagger URL i.e accessing all the URLs through one - spring-boot

I have around 16 microservices built in Spring Boot that communicate with each other and each microservice is having multiple APIs in it. I have configured OpenAPI for each of the microservices. So it gives me 16 "swagger-ui" URLs.
I wonder how can I centralize all Swagger URLs on one single page; I want only 1 swagger URL to access all the 16 microservices.
I have gone through the following as well but didn't get any solution
Centralize Swagger at one place for All microservices
Please guide me to the best way to achieve it.

I found a working solution on GitHub. It works with Spring Cloud Discovery but can be easily adapted to other discovery solutions. The basic idea is to generate for Swagger the list of URLs pointing to the OpenAPI JSON files.
The result is similar to one in the #shadyx's answer, but the list is generated dynamically
#RestController
public class SwaggerUiConfig {
#Autowired
private DiscoveryClient discoveryClient;
#GetMapping("/swagger-config.json")
public Map<String, Object> swaggerConfig() {
List<SwaggerUrl> urls = new LinkedList<>();
discoveryClient.getServices().forEach(serviceName ->
discoveryClient.getInstances(serviceName).forEach(serviceInstance ->
urls.add(new SwaggerUrl(serviceName, serviceInstance.getUri() + "/v3/api-docs"))
)
);
return Map.of("urls", urls);
}
}
application.yaml
springdoc:
swagger-ui:
configUrl: "/swagger-config.json"
Sample of the generated swagger-config.json
{
"urls": [
{
"url": "http://localhost:8088/v3/api-docs/users",
"name": "users"
},
{
"url": "http://localhost:8088/v3/api-docs/stores",
"name": "stores"
}
]
}

If all you µservices use the same host, you can create a Spring Boot µservice which acts as a openAPI gateway using spring-doc. (If they are not using the same host, you will have to deal with CORS issues)
First, add the following dependencies to your new gateway µservice
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Then, add the below configuration
springdoc:
api-docs:
enabled: true
swagger-ui:
configUrl: ${server.servlet.contextPath}/v3/api-docs/swagger-config
url: ${server.servlet.contextPath}/v3/api-docs
urls:
- name: api-customer
url: /customer/v3/api-docs
- name: api-cart
url: /cart/v3/api-docs
- name: api-product
url: /product/v3/api-docs
For each µservice you have to fill in the url property with is the url to the openAPI definition at json or yaml format.
For springdoc library which uses openAPI 3.0 the default url is /${µServiceContextPath}/v3/api-docs
For springfox library which uses openAPI 2.0 the default url is /${µServiceContextPath}/v2/api-docs
Finally access to /${contextPath}/swagger-ui.html and you will be able to select an openAPI

Related

How can I integrate filters defined using javax.ws.rs in to Spring WebClient?

I want to use apache.cxf filter CreateSignatureInterceptor in the spring WebClient but not able to do it.
The interceptor CreateSignatureInterceptor is found in the below dependency
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-security-http-signature</artifactId>
<version>3.4.5</version>
</dependency>
The way I am creating the Spring WebClient is as below
private val webClient = WebClient.builder()
.filter(logRequest())
.clientConnector(JettyClientHttpConnector(httpClient))
.baseUrl(baseUri)
.build()
private fun logRequest(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor{
log.info(it.url().toString())
// --> how to add the existing apache filters
// CreateSignatureInterceptor()
Mono.just(it)
}
}
In this, we can create or add filters in spring WebClient like logRequest() but I am not sure how to integrate the existing defined filters.
As there are very less sources I find it hard to define the existing filters in the spring webclient.

How can I add swagger open api 3.0 configuration for secured api's

I've enabled the Swagger open API 3.0 like below:
Added following dependencies
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.2</version>
</dependency>
After that added below bean for customization.
#Bean
public OpenAPI customOpenAPI(#Value("${application-description}") String appDesciption,
#Value("${application-version}") String appVersion) {
return new OpenAPI().info(new Info().title("One Money Backend API").version(appVersion)
.description(appDesciption).termsOfService("http://swagger.io/terms/")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
All is working fine. I'm able to access swagger-ui and api-docs.
But I've few api's which is secured with keycloak OAuth 2.0. When I'm trying to access those secured api's I'm getting unauthorized error (it is expected). I want to test secured api's as well from swagger open api 3.0.
Can some one please help me to enable authorization in swagger open api 3.0 to test my secured api's from swagger.
You need to add SecurityScheme bean as shown below. Based on your authentication type you can change the scheme
for basic
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI().components(new Components()
.addSecuritySchemes("basicScheme", new SecurityScheme()
.type(SecurityScheme.Type.HTTP).scheme("basic")));
}
for JWT
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI().components(new Components()
.addSecuritySchemes("bearer-key", new SecurityScheme()
.type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")));
}
More details can be found here

Why X-RateLimit-Remaining -1 in response header while using spring cloud api gateway ratelimit with redis?

I implemented ratelimit with redis in my spring cloud api gateway. Here is part of application.yml:
spring:
cloud:
gateway:
httpclient:
ssl:
useInsecureTrustManager: true
discovery:
locator:
enabled: true
routes:
- id: test-rest-service
uri: lb://test-rest-service
predicates:
- Path=/test/**
filters:
- RewritePath=/test/(?<path>.*), /$\{path}
- name: RequestRateLimiter
args:
key-resolver: "#{#userRemoteAddressResolver}"
redis-rate-limiter.replenishRate: 2
redis-rate-limiter.burstCapacity: 3
I called a GET API via postman and checked response header.
X-RateLimit-Remaining -1
X-RateLimit-Burst-Capacity 3
X-RateLimit-Replenish-Rate 2
The rate limit is not working. Why am I getting negative value for X-RateLimit-Remaining? What does it mean? How do I fix it?
This happened to me because there was no Redis instance launched. You have two options:
1) Download and run a Redis instance using docker:
docker run --name redis -d redis
2) You can use in testing an Embedded Redis Server as it is explained in the following article by adding the maven dependency:
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
And including the following snippet:
#TestConfiguration
public class TestRedisConfiguration {
private RedisServer redisServer;
public TestRedisConfiguration() {
this.redisServer = new RedisServer(6379);
}
#PostConstruct
public void postConstruct() {
redisServer.start();
}
#PreDestroy
public void preDestroy() {
redisServer.stop();
}
}
I faced the same issue recently. In my case, there was an older version of Redis installed which caused X-RateLimit-Remaining to be set to -1 constantly.
redis-cli shutdown

Spring Cloud Feign/Ribbon with corporate proxy

I want to consume a REST service from the outside world behind a corporate proxy with authentication.
How do I configure Spring Boot + Spring Cloud Feign/Ribbon to use our proxy?
I believe you're looking for something like this:
import feign.Feign;
import okhttp3.OkHttpClient;
import java.net.InetSocketAddress;
import java.net.Proxy;
...
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy-url", 1234));
OkHttpClient okHttpClient = new OkHttpClient.Builder().proxy(proxy).build();
Feign.builder()
.client(new feign.okhttp.OkHttpClient(okHttpClient))
.target(...);
You just have to additionally add compile 'io.github.openfeign:feign-okhttp:9.5.0' to your project.
The target clause contains your defined Interface. Further reference: https://github.com/OpenFeign/feign
Turns out there is actually a much easier solution.
The following information will be helpful (also for more advanced use cases):
Spring Cloud Commons HTTP Factories
Overriding Feign Defaults
OpenFeign Client can run with several HTTP Clients.
By default it uses java.net.URLConnection, but you can also use ApacheHttpClient or OkHttpClient.
Using Apache Http Client
Here is what you can do to set a proxy using ApacheHttpClient:
Add the following two dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Dependency to switch HttpClient implementation from java.net.URLConnection to Apache HTTP Client -->
<!-- See also: FeignAutoConfiguration for details. -->
<!-- See also: https://cloud.spring.io/spring-cloud-commons/reference/html/#http-clients -->
<!-- See also: https://cloud.spring.io/spring-cloud-openfeign/reference/html/#spring-cloud-feign-overriding-defaults -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
In your app expose the following bean:
// see: https://cloud.spring.io/spring-cloud-commons/reference/html/#http-clients
#Bean
public HttpClientBuilder proxiedHttpClient() {
String proxyHost = "client-envoy";
Integer proxyPort = 80
String proxyScheme = "http";
return HttpClientBuilder.create()
.setProxy(new HttpHost(proxyHost, proxyPort, proxyScheme));
}
That's it - nothing else needs to be configured in application.yaml since ApacheHttpClient will be used by default, if it is on the classpath.
Using Ok Http Client
To set a proxy using OkHttpClient you do a similar thing:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
In your application.yml make sure to enable OkHttpClient and disable ApacheHttpClient:
spring:
cloud:
httpclientfactories:
ok:
enabled: true
apache:
enabled: false
feign:
okhttp:
enabled: true
httpclient:
enabled: false
Instead of HttpClientBuilder expose a bean of type OkHttpClient.Builder.
Spring cloud feign supports three underlying implementations:
Default
Apache HttpClient
OkHttpClient
If using Default:
Create this spring bean (say by defining inside class with #Configuration annotation), no changes required in application properties/yml:
#Bean
public Client feignClient() {
return new Client.Proxied(
null, null, new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
}
If using Apache HttpClient:
that means you have feign.httpclient.enabled: true in application.yml and below in your pom.xml or build.gradle:
pom.xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
build.gradle
implementation 'io.github.openfeign:feign-httpclient'
Create this spring bean (say by defining inside class with #Configuration annotation):
#Bean
public CloseableHttpClient feignClient() {
return HttpClientBuilder.create().setProxy(new HttpHost(proxyHost, proxyPort)).build();
}
If using OkHttpClient:
that means you have feign.okhttp.enabled: true in application.yml and below in your pom.xml or build.gradle:
pom.xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
build.gradle
implementation 'io.github.openfeign:feign-okhttp'
Create this spring bean (say by defining inside class with #Configuration annotation):
#Bean
public OkHttpClient feignClient() {
return new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)))
.build();
}

Cannot disable Spring Cloud Config via spring.cloud.config.enabled:false

Let me preface this by saying that I'm not using Spring Cloud Config directly, it is transitive via Spring Cloud Hystrix starter.
When only using #EnableHystrix, Spring Cloud also tries to locate a configuration server, expectedly unsuccessfully, since I'm not using one. The application works just fine, as far as I can tell, but the problem is in the status checks. Health shows DOWN because there is no config server.
Browsing the source of the project, I'd expect spring.cloud.config.enabled=false to disable this functionality chain, however this is not what I'm seeing.
After upgrading to 1.0.0.RC1 (which adds this property) and using #EnableCircuitBreaker:
{
status: "DOWN",
discovery: {
status: "DOWN",
discoveryClient: {
status: "DOWN",
error: "org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.cloud.client.discovery.DiscoveryClient] is defined"
}
},
diskSpace: {
status: "UP",
free: 358479622144,
threshold: 10485760
},
hystrix: {
status: "UP"
},
configServer: {
status: "DOWN",
error: "org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http: //localhost: 8888/bootstrap/default/master":Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect"
}
}
After checking the configprops endpoint, it seems that my properties are being overridden. Note that the parent has the configClient enabled.
parent: {
configClientProperties: {
prefix: "spring.cloud.config",
properties: {
password: null,
discovery: {
enabled: false,
serviceId: "CONFIGSERVER"
},
name: "bootstrap",
label: "master",
env: "default",
uri: "http://localhost:8888",
enabled: true,
failFast: false,
username: null
}
}
},
configClientProperties: {
prefix: "spring.cloud.config",
properties: {
password: null,
discovery: {
enabled: false,
serviceId: "CONFIGSERVER"
},
name: "bootstrap",
label: "master",
env: "default",
uri: "http://localhost:8888",
enabled: false,
failFast: false,
username: null
}
}
Any direction would be appreciated, if it seems I'm not doing this correctly.
The config server is needed during bootstrap, and that's where the parent property sources come from. It looks like all you need to do is move your spring.cloud.config.enabled property to bootstrap.yml (or .properties).
You can put a bootstrap properties or yml to your resource direcotry
or your applications directory and add
spring.cloud.config.enabled=false. OR
You can disable spring cloud config server client by adding an environment variable: SPRING_CLOUD_CONFIG_ENABLED=false OR
Config server client can be disabled by adding a parameter to your app, if you pass the args to parameters to SpringApplication.run:
public static void main(String[] args) throws Exception {
SpringApplication.run(YourApplication.class, args);
}
and start the app by:
java -jar yourapplication.jar --spring.cloud.config.enabled=false
I had the same problem, I wanted to have the config server disabled (as we do not need it so far) but the property mentioned above is not correct for RC1 at least.
spring.cloud.enabled
Should be:
spring.cloud.config.enabled
I tried all of the above changes and still config client never stopped somehow.
The only way I was able to disable it by using following exclusion in my project's pom.xml file.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</exclusion>
</exclusions>
</dependency>
Regarding the discovery service followup (looks like no other posts on that), setting spring.cloud.config.discovery.enabled: false worked for me, but only if it was set in bootstrap(yml/properties) and if I removed the #EnableDiscoveryClient annotation from my Application class. I guess this means one cannot use that annotation for any service where discovery will not be used at times.
None of those has helped me, I needed to disable spring cloud client bootstrap from server for my integrations tests, so whoever faces the same issue may use what as helped me:
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = PropertySourceBootstrapConfiguration.class),
})
public class TestApplication {
}
and than annotate your tests with:
#SpringBootTest(classes = TestApplication.class)
class SomeIntegrationTest {}

Resources