Swagger, Spring Boot 2 With a Reverse-Proxy in Docker
This article demonstrates how you can set up Spring Boot 2 with Swagger behind a reverse-proxy locally using NGINX and Docker.
In our use case, we needed to test X-Forwarded-* headers with a Spring Boot service to emulate how an authorization server would deal with forwarding requests.
In this example, the reverse proxy will have a route published on the public side (defaulting to port 80 for http traffic, 433 for https). The reverse-proxy links the public URL (i.e. https://example.com) to a service on the internal DNS, and forwards the request — setting the required X-Forwarded-* headers in the process.
This is a standard authentication/authorization pattern that can be used to block specific connections, authenticate, validate or change headers, etc.
To replicate this locally, we used NGINX as a reverse-proxy server. We did not want to pollute this example with SSL/HTTPS configuration, so we have just used http://localhost on port 80.
We can also extend the use of NGINX to map multiple backend services to the reverse-proxy, by adding locations to the services of the NGINX section.
Configuring Swagger and Spring Boot
For this example we use springdoc-openapi,
which is a fully-compliant OpenApi 3.0 implementation for Spring Boot.
The configuration for Swagger was left to Spring Boot defaults except the path, defined in the application configuration:
Swagger relies on internal routing to make requests from the clients perspective. Putting the service behind a reverse-proxy without providing the X-Forwarded headers will result in the user not being able to use the documentation as intended. It will also expose internal configuration to the users regarding the host and ports used for the service.
The default in most cases is for X-Forwarded-*
headers to be disabled. This means that we would need to enable these for Spring Boot.
Since Spring Boot Release 2.2, you can set the forward-headers-strategy
to framework
in the application config, which allows headers with the prefix X-Forwarded-*
through into the running service.
Setting the strategy to native tells Spring Boot to inherit the X-Forwarded settings from the container/runtime environment.
If you are using a version of Spring Boot before Release 2.2, you will need to create a ForwardHeadersStrategy
to achieve the same outcome.
Note on Security
The services that you allow X-Forwarded-* headers on should trust the source of these requests.
The reason that in most cases it is set to not allow these headers by default, is because of security concerns with cache poisoning and spoofing.
Good practise would be: X-Forwarded-Host
and X-Forwarded-Proto
to be cleared and reset by the reverse-proxy and not just allowing them through. If these headers are required, then you should put appropriate sanitation in place.
Getting the Service Running in Docker
The DockerFile used for the Spring Boot 2 application:
Example of the docker-compose.yaml
file that was used for NGINX and the container built from the above DockerFile.
For the purpose of this example, we only expose port 80 for NGINX. This is insecure and in production systems it is expected that you have the correct security using port 443 in place.
Docker has it’s own internal DNS, and in this case we use the depends-on and links to the swagger-service. Because of this, we do not need to expose any of the ports to localhost, ensuring the only way of accessing the service is through the proxy.
…
Configuring the Reverse-Proxy
We have shown how we can get the services running in docker. Currently, it will not work because we have not told it where to route the requests. We need to provide NGINX with the required configuration.
In the example nginx.conf
file, we are routing requests on the root path to the swagger-service. We have setup the reverse proxy as http
only, listening on port 80
(IPv4 and IPv6).
The object location
refers to the path of the host server (NGINX). The proxy_pass
refers to the location of where to forward those requests, and the proxy_set_header
allows us to intercept and replace/add the X-Forwarded-* headers.
Pattern matching on the inputs, instead of explicitly replacing then, would be sufficient to protect against most of the vulnerabilities specified before.
Note that events {}
was required in config for NGINX to boot correctly.
Running Everything Together
Click on this link to see a complete working example
To build the service that will host the swagger documentation, we use Gradle:
./gradlew clean build -x test
We want to build the docker image that will be used before we can run it:
docker-compose build
Then we want to launch the swagger service and the NGINX reverse-proxy:
docker-compose up
Summary
This is a worked example of getting an NGINX reverse-proxy working with Swagger and Spring Boot 2.
This was originally put together to reproduce a production issue that we had found locally for understanding and debugging purposes.
You require changes to Spring Boot configuration to get everything working properly, and your reverse-proxy (or identity server implementation) also needs to allow the X-Forwarded headers through, whilst still being aware of the potential introduction of security vulnerabilities.
The code examples within this article are available on GitHub